Generator 自动执行器 (run 函数) 深度解析
·
Generator 自动执行器 (run 函数) 深度解析
概述
run 函数是一个 Generator 自动执行器,用于自动驱动 Generator 函数执行,让异步代码可以用同步的方式编写。它是 async/await 出现之前,JavaScript 社区处理异步流程的重要模式。
核心代码
function run(gen) {
var args = [].slice.call(arguments, 1);
var it = gen.apply(this, args);
return new Promise(function(resolve, reject) {
function handleNext(value) {
try {
var next = it.next(value);
handleResult(next);
} catch (e) {
reject(e);
}
}
function handleResult(next) {
if (next.done) {
resolve(next.value);
} else {
Promise.resolve(next.value)
.then(handleNext)
.catch(function(err) {
try {
handleResult(it.throw(err));
} catch (e) {
reject(e);
}
});
}
}
handleNext();
});
}
一、设计原理与思想
1.1 Generator 与异步的关系
Generator 函数有两个关键特性使其适合处理异步:
| 特性 | 说明 | 异步应用 |
|---|---|---|
| 可暂停执行 | yield 会暂停函数,交出控制权 | 等待异步操作完成 |
| 双向通信 | yield 接收外部传入的值 | 异步结果回传给 Generator |
function* main() {
// yield 暂停,等待 Promise 完成
// 完成后,结果通过 it.next(value) 传回
let a = yield fetchData(10, 100);
// ↑ ↑
// yield 出 Promise 结果传回给 a
}
1.2 自动执行器的核心任务
执行器需要解决三个核心问题:
- 自动推进:检测 yield 出的 Promise,等待完成后自动继续
- 值传递:将 Promise 的 resolve 值传回 Generator
- 异常传递:将 Promise 的 reject 错误抛回 Generator
1.3 为什么需要 Promise 包装?
return new Promise(function(resolve, reject) { ... });
- 统一返回值类型,调用者可以用
.then()获取结果 - 将内部异常正确传递给外部
- 支持 async/await 调用:
await run(main)
二、核心函数深度解析
2.1 初始化阶段
var args = [].slice.call(arguments, 1);
var it = gen.apply(this, args);
参数处理解析:
// 支持向 Generator 传参
run(main, arg1, arg2, arg3);
// 等价于
main(arg1, arg2, arg3);
为什么用 gen.apply(this, args)?
apply可以接受数组形式的参数- 保持
this上下文(虽然通常用不到)
2.2 handleNext 函数详解
function handleNext(value) {
try {
var next = it.next(value);
handleResult(next);
} catch (e) {
reject(e);
}
}
参数 value 的来源:
| 调用时机 | value 值 |
|---|---|
首次调用 handleNext() |
undefined(无参数) |
| Promise resolve 后 | Promise 的 resolve 值 |
try-catch 的作用:
捕获 it.next(value) 可能抛出的异常:
function* main() {
throw new Error("同步错误");
yield Promise.resolve(1);
}
// it.next() 会直接抛出 Error("同步错误")
2.3 handleResult 函数详解
function handleResult(next) {
if (next.done) {
resolve(next.value);
} else {
Promise.resolve(next.value)
.then(handleNext)
.catch(function(err) {
try {
handleResult(it.throw(err));
} catch (e) {
reject(e);
}
});
}
}
Promise.resolve(next.value) 的妙用:
// 情况1:yield 出的是 Promise
yield fetchData(10, 100);
// Promise.resolve(promise) === promise(原样返回)
// 情况2:yield 出的是普通值
yield 42;
// Promise.resolve(42) 创建一个 resolve(42) 的 Promise
// 好处:统一处理,无需判断类型
递归调用分析:
handleResult(next)
│
└── .then(handleNext)
│
└── it.next(value)
│
└── handleResult(next)
│
└── ... 循环继续
三、异常处理机制深度解析
3.1 异常传播的完整路径
Promise reject
│
▼
.catch(function(err) { ... })
│
▼
it.throw(err)
│
├──▶ Generator 内部 try-catch 捕获
│ │
│ ├── 继续执行 ──▶ { done: false, value: ... }
│ │
│ └── 返回 ──▶ { done: true, value: ... }
│
└──▶ Generator 未捕获
│
▼
抛出异常
│
▼
try-catch 捕获 ──▶ reject(e)
3.2 三种异常场景详解
场景1:捕获后继续执行
function* main() {
let a;
try {
a = yield Promise.reject("错误1");
} catch (err) {
console.log("捕获:", err);
a = 100; // 恢复执行,给默认值
}
// 继续执行后续代码
let b = yield Promise.resolve(a * 2);
return b;
}
// 执行过程:
// 1. it.next() → { done: false, value: Promise.reject("错误1") }
// 2. Promise reject → .catch 捕获
// 3. it.throw("错误1") → Generator catch 捕获,a = 100
// 4. 继续执行到下一个 yield → { done: false, value: Promise.resolve(200) }
// 5. Promise resolve → handleNext(200)
// 6. it.next(200) → { done: true, value: 200 }
// 7. resolve(200)
场景2:捕获后返回
function* main() {
try {
yield Promise.reject("错误2");
} catch (err) {
return "降级结果"; // 提前返回
}
// 这里的代码不会执行
yield Promise.resolve("不会到达");
}
// 执行过程:
// 1. it.next() → { done: false, value: Promise.reject("错误2") }
// 2. Promise reject → .catch 捕获
// 3. it.throw("错误2") → Generator catch 捕获,return "降级结果"
// 4. 返回 { done: true, value: "降级结果" }
// 5. handleResult → resolve("降级结果")
场景3:未捕获异常
function* main() {
// 没有 try-catch
let a = yield Promise.reject("未捕获错误");
return a;
}
// 执行过程:
// 1. it.next() → { done: false, value: Promise.reject("未捕获错误") }
// 2. Promise reject → .catch 捕获
// 3. it.throw("未捕获错误") → Generator 内部抛出异常
// 4. try-catch 捕获 → reject("未捕获错误")
3.3 为什么用 handleResult 处理 it.throw(err)?
核心原因:it.throw(err) 返回迭代器对象,不是只抛异常
// it.throw(err) 的返回值类型
{ done: boolean, value: any }
// 与 it.next() 返回值类型完全相同
{ done: boolean, value: any }
设计意义:
Generator 的异常处理是"可恢复的"。即使发生错误,Generator 也可以:
- 捕获错误后继续执行(返回
{ done: false }) - 捕获错误后返回结果(返回
{ done: true })
因此需要用 handleResult 统一处理这两种情况。
四、执行流程可视化
4.1 完整执行流程图
┌─────────────────────────────────────────────────────────────────┐
│ run(main) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────┐
│ handleNext() │◄─────────────────────┐
└─────────────────┘ │
│ │
▼ │
┌─────────────────┐ │
│ it.next(value) │ │
└─────────────────┘ │
│ │
▼ │
┌─────────────────┐ │
│ handleResult() │ │
└─────────────────┘ │
│ │
┌───────────────┴───────────────┐ │
│ │ │
▼ ▼ │
┌─────────────────┐ ┌─────────────────┐ │
│ done: true │ │ done: false │ │
└─────────────────┘ └─────────────────┘ │
│ │ │
▼ ▼ │
┌─────────────────┐ ┌─────────────────┐ │
│ resolve(value) │ │ Promise.resolve │ │
│ │ │ (next.value) │ │
└─────────────────┘ └─────────────────┘ │
│ │ │
▼ ┌────────┴────────┐ │
┌──────────┐ │ │ │
│ 结束 │ ▼ ▼ │
└──────────┘ ┌─────────────┐ ┌─────────────┐│
│ .then( │ │ .catch( ││
│ handleNext) │ │ err处理) ││
└─────────────┘ └─────────────┘│
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │it.throw(err)│ │
│ └─────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │handleResult │ │
│ └─────────────┘ │
│ │ │
└─────────────────┼──────┘
│
┌─────────────────┘
│
▼
(循环继续...)
4.2 时序图
run() handleNext() it.next() handleResult() Promise
│ │ │ │ │
│──handleNext()──▶│ │ │ │
│ │──it.next()────▶│ │ │
│ │ │──{done,value}─▶│ │
│ │ │ │──resolve()───▶│
│ │ │ │ │
│ │ │ │◀──value───────│
│ │◀──handleNext()─│◀───────────────│ │
│ │ │ │ │
│ │──it.next(val)─▶│ │ │
│ │ │──{done,value}─▶│ │
│ │ │ │──resolve()───▶│
│ │ │ │ │
│ │ │ │◀──value───────│
│ │ │ │ │
│ │ │ │──done:true───▶│
│◀─────────────────────────────────────────────────resolve(value) │
│ │
五、与 async/await 对比
5.1 代码对比
使用 run + Generator:
function* main() {
let a = yield fetchData(10, 100);
let b = yield fetchData(a, 100);
return b;
}
run(main).then(result => console.log(result));
使用 async/await:
async function main() {
let a = await fetchData(10, 100);
let b = await fetchData(a, 100);
return b;
}
main().then(result => console.log(result));
5.2 功能对比表
| 特性 | run + Generator | async/await |
|---|---|---|
| 语法 | yield |
await |
| 函数声明 | function* |
async function |
| 返回值 | 需要包装 | 自动返回 Promise |
| 错误处理 | 需要 run 函数支持 | 原生支持 |
| 浏览器支持 | ES6 (2015) | ES8 (2017) |
| 调试体验 | 较差 | 更好的堆栈跟踪 |
| 性能 | 略低 | 更优 |
5.3 async/await 的本质
async/await 可以理解为 Generator + 自动执行器的语法糖:
// async/await 本质上等价于
async function main() {
let a = await fetchData(10, 100);
return a;
}
// 等价于
function* main() {
let a = yield fetchData(10, 100);
return a;
}
run(main);
六、边界情况处理
6.1 yield 非 Promise 值
function* main() {
let a = yield 42; // 普通值
let b = yield "hello"; // 字符串
let c = yield null; // null
return [a, b, c];
}
// Promise.resolve(42) 会将其包装为 Promise
// 结果: [undefined, undefined, undefined]
// 因为普通值没有 resolve 值传递
6.2 空 Generator
function* main() {
// 空函数
}
run(main).then(result => {
console.log(result); // undefined
});
6.3 同步返回
function* main() {
return "直接返回";
yield Promise.resolve(1); // 不会执行
}
run(main).then(result => {
console.log(result); // "直接返回"
});
6.4 嵌套 Generator
function* inner() {
let x = yield Promise.resolve(10);
return x * 2;
}
function* outer() {
// 需要 run 包装才能正确执行
let result = yield run(inner);
return result + 5;
}
run(outer).then(r => console.log(r)); // 25
6.5 并行执行
function* main() {
// 错误:串行执行
let a = yield fetchData(10, 100);
let b = yield fetchData(20, 100); // 等待 a 完成后才开始
// 正确:并行执行
let [c, d] = yield Promise.all([
fetchData(10, 100),
fetchData(20, 100)
]);
}
七、性能与优化
7.1 调用栈分析
每次 yield 都会创建新的 Promise 链:
handleResult → .then → handleNext → it.next → handleResult → ...
潜在问题: 深度递归可能导致调用栈增长
解决方案: 使用 setImmediate 或 process.nextTick 断开调用链
// 优化版本
function handleResult(next) {
if (next.done) {
resolve(next.value);
} else {
Promise.resolve(next.value)
.then(value => {
// 断开调用链
setImmediate(() => handleNext(value));
})
.catch(...);
}
}
7.2 内存考量
- 每个 yield 创建一个 Promise
- 长时间运行的 Generator 可能积累内存
- 建议:及时 return 结束 Generator
八、实际应用场景
8.1 数据库事务
function* transaction() {
try {
let conn = yield getConnection();
yield conn.query("BEGIN");
yield conn.query("INSERT INTO users ...");
yield conn.query("UPDATE accounts ...");
yield conn.query("COMMIT");
return { success: true };
} catch (err) {
yield conn.query("ROLLBACK");
throw err;
}
}
8.2 重试机制
function* fetchWithRetry(url, maxRetries) {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
return yield fetch(url);
} catch (err) {
lastError = err;
yield delay(1000 * Math.pow(2, i)); // 指数退避
}
}
throw lastError;
}
8.3 流程控制
function* workflow() {
const user = yield getUser(userId);
const orders = yield getOrders(user.id);
const payments = yield getPayments(orders);
return { user, orders, payments };
}
九、常见问题与陷阱
9.1 忘记 yield
function* main() {
// 错误:忘记 yield,Promise 不会等待
let a = fetchData(10, 100); // a 是 Promise,不是 20
// 正确
let a = yield fetchData(10, 100); // a 是 20
}
9.2 错误的异常捕获
function* main() {
// 错误:try-catch 只能捕获 yield 的错误
try {
let a = yield fetchData(10, 100);
a.nonExistentMethod(); // 这个错误不会被捕获!
} catch (err) {
console.log(err);
}
}
9.3 this 绑定问题
const obj = {
value: 42,
*gen() {
return this.value; // 正确
}
};
run(obj.gen); // this 可能丢失
// 解决方案
run(obj.gen.bind(obj));
// 或
run(function*() { return obj.gen(); });
十、总结
核心要点
- 自动执行原理:通过递归调用
handleNext和handleResult自动推进 Generator - 值传递机制:
it.next(value)将 Promise 结果传回 Generator - 异常传播:
it.throw(err)将错误抛回 Generator 内部处理 - Promise.resolve 妙用:统一处理 Promise 和普通值
设计精髓
Generator 提供暂停/恢复能力
+
Promise 提供异步结果
+
run 函数提供自动驱动
=
同步风格的异步代码
学习价值
虽然 async/await 已经成为主流,但理解 run 函数的实现有助于:
- 深入理解 JavaScript 异步机制
- 理解 async/await 的底层原理
- 在特殊场景下灵活运用
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)