Generator 自动执行器 (run 函数) 深度解析
Generator 自动执行器 (run 函数) 深度解析
概述
run函数是一个 Generator 自动执行器,用于自动驱动 Generator 函数执行,让异步代码可以用同步的方式编写。它是 async/await 出现之前,JavaScript 社区处理异步流程的重要模式。
核心代码
functionrun(gen){varargs=[].slice.call(arguments,1);varit=gen.apply(this,args);returnnewPromise(function(resolve,reject){functionhandleNext(value){try{varnext=it.next(value);handleResult(next);}catch(e){reject(e);}}functionhandleResult(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) 传回leta=yieldfetchData(10,100);// ↑ ↑// yield 出 Promise 结果传回给 a}1.2 自动执行器的核心任务
执行器需要解决三个核心问题:
- 自动推进:检测 yield 出的 Promise,等待完成后自动继续
- 值传递:将 Promise 的 resolve 值传回 Generator
- 异常传递:将 Promise 的 reject 错误抛回 Generator
1.3 为什么需要 Promise 包装?
returnnewPromise(function(resolve,reject){...});- 统一返回值类型,调用者可以用
.then()获取结果 - 将内部异常正确传递给外部
- 支持 async/await 调用:
await run(main)
二、核心函数深度解析
2.1 初始化阶段
varargs=[].slice.call(arguments,1);varit=gen.apply(this,args);参数处理解析:
// 支持向 Generator 传参run(main,arg1,arg2,arg3);// 等价于main(arg1,arg2,arg3);为什么用gen.apply(this, args)?
apply可以接受数组形式的参数- 保持
this上下文(虽然通常用不到)
2.2 handleNext 函数详解
functionhandleNext(value){try{varnext=it.next(value);handleResult(next);}catch(e){reject(e);}}参数value的来源:
| 调用时机 | value 值 |
|---|---|
首次调用handleNext() | undefined(无参数) |
| Promise resolve 后 | Promise 的 resolve 值 |
try-catch 的作用:
捕获it.next(value)可能抛出的异常:
function*main(){thrownewError("同步错误");yieldPromise.resolve(1);}// it.next() 会直接抛出 Error("同步错误")2.3 handleResult 函数详解
functionhandleResult(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 出的是 PromiseyieldfetchData(10,100);// Promise.resolve(promise) === promise(原样返回)// 情况2:yield 出的是普通值yield42;// 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(){leta;try{a=yieldPromise.reject("错误1");}catch(err){console.log("捕获:",err);a=100;// 恢复执行,给默认值}// 继续执行后续代码letb=yieldPromise.resolve(a*2);returnb;}// 执行过程:// 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{yieldPromise.reject("错误2");}catch(err){return"降级结果";// 提前返回}// 这里的代码不会执行yieldPromise.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-catchleta=yieldPromise.reject("未捕获错误");returna;}// 执行过程:// 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(){leta=yieldfetchData(10,100);letb=yieldfetchData(a,100);returnb;}run(main).then(result=>console.log(result));使用 async/await:
asyncfunctionmain(){leta=awaitfetchData(10,100);letb=awaitfetchData(a,100);returnb;}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 本质上等价于asyncfunctionmain(){leta=awaitfetchData(10,100);returna;}// 等价于function*main(){leta=yieldfetchData(10,100);returna;}run(main);六、边界情况处理
6.1 yield 非 Promise 值
function*main(){leta=yield42;// 普通值letb=yield"hello";// 字符串letc=yieldnull;// nullreturn[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"直接返回";yieldPromise.resolve(1);// 不会执行}run(main).then(result=>{console.log(result);// "直接返回"});6.4 嵌套 Generator
function*inner(){letx=yieldPromise.resolve(10);returnx*2;}function*outer(){// 需要 run 包装才能正确执行letresult=yieldrun(inner);returnresult+5;}run(outer).then(r=>console.log(r));// 256.5 并行执行
function*main(){// 错误:串行执行leta=yieldfetchData(10,100);letb=yieldfetchData(20,100);// 等待 a 完成后才开始// 正确:并行执行let[c,d]=yieldPromise.all([fetchData(10,100),fetchData(20,100)]);}七、性能与优化
7.1 调用栈分析
每次 yield 都会创建新的 Promise 链:
handleResult → .then → handleNext → it.next → handleResult → ...潜在问题:深度递归可能导致调用栈增长
解决方案:使用setImmediate或process.nextTick断开调用链
// 优化版本functionhandleResult(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{letconn=yieldgetConnection();yieldconn.query("BEGIN");yieldconn.query("INSERT INTO users ...");yieldconn.query("UPDATE accounts ...");yieldconn.query("COMMIT");return{success:true};}catch(err){yieldconn.query("ROLLBACK");throwerr;}}8.2 重试机制
function*fetchWithRetry(url,maxRetries){letlastError;for(leti=0;i<maxRetries;i++){try{returnyieldfetch(url);}catch(err){lastError=err;yielddelay(1000*Math.pow(2,i));// 指数退避}}throwlastError;}8.3 流程控制
function*workflow(){constuser=yieldgetUser(userId);constorders=yieldgetOrders(user.id);constpayments=yieldgetPayments(orders);return{user,orders,payments};}九、常见问题与陷阱
9.1 忘记 yield
function*main(){// 错误:忘记 yield,Promise 不会等待leta=fetchData(10,100);// a 是 Promise,不是 20// 正确leta=yieldfetchData(10,100);// a 是 20}9.2 错误的异常捕获
function*main(){// 错误:try-catch 只能捕获 yield 的错误try{leta=yieldfetchData(10,100);a.nonExistentMethod();// 这个错误不会被捕获!}catch(err){console.log(err);}}9.3 this 绑定问题
constobj={value:42,*gen(){returnthis.value;// 正确}};run(obj.gen);// this 可能丢失// 解决方案run(obj.gen.bind(obj));// 或run(function*(){returnobj.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 的底层原理
- 在特殊场景下灵活运用
