面试官问 async、await 函数原理是在问什么?

2025-05-29 0 98

面试官问 async、await 函数原理是在问什么?

1. 前言

这周看的是 co 的源码,我对 co 比较陌生,没有了解和使用过。因此在看源码之前,我希望能大概了解 co 是什么,解决了什么问题。

2. 简单了解 co

先看了 co 的 GitHub,README 是这样介绍的:

Generator based control flow goodness for nodejs and the browser, using promises, letting you write non-blocking code in a nice-ish way.

看起来有点懵逼,又查了一些资料,大多说 co 是用于 generator 函数的自动执行。generator 是 ES6 提供的一种异步编程解决方案,它最大的特点是可以控制函数的执行。

2.1 关于 generator

说到异步编程,我们很容易想到还有 promise,async 和 await。它们有什么区别呢?先看看 JS 异步编程进化史:callback -> promise -> generator -> async + await

面试官问 async、await 函数原理是在问什么?

JS 异步编程

再看看它们语法上的差异:

Callback Promise Generator async + await + Promise
ajax(url, () => {}) Promise((resolve,reject) => { resolve() }).then() function* gen() { yield 1} async getData() { await fetchData() }

关于 generator 的学习不在此篇幅详写了,需要了解它的概念和语法。

3. 学习目标

经过简单学习,大概明白了 co 产生的背景,因为 generator 函数不会自动执行,需要手动调用它的 next() 函数,co 的作用就是自动执行 generator 的 next() 函数,直到 done 的状态变成 true 为止。

那么我这一期的学习目标:

1)解读 co 源码,理解它是如何实现自动执行 generator

2)动手实现一个简略版的 co

4. 解读 co 源码

co 源码地址:https://github.com/tj/co

4.1 整体架构

从 README 中,可以看到是如何使用 co :

  1. co(function*(){
  2. varresult=yieldPromise.resolve(true);
  3. returnresult;
  4. }).then(function(value){
  5. console.log(value);
  6. },function(err){
  7. console.error(err.stack);
  8. });

从代码可以看到它接收了一个 generator 函数,返回了一个 Promise,这部分对应的源码如下。

  1. functionco(gen){
  2. varctx=this;
  3. //获取参数
  4. varargs=slice.call(arguments,1);
  5. //返回一个Promise
  6. returnnewPromise(function(resolve,reject){
  7. //把ctx和参数传递给gen函数
  8. if(typeofgen==='function')gengen=gen.apply(ctx,args);
  9. //判断gen.next是否函数,如果不是直接resolve(gen)
  10. if(!gen||typeofgen.next!=='function')returnresolve(gen);
  11. //先执行一次next
  12. onFulfilled();
  13. //实际上就是执行gen.next函数,获取gen的值
  14. functiononFulfilled(res){
  15. varret;
  16. try{
  17. ret=gen.next(res);
  18. }catch(e){
  19. returnreject(e);
  20. }
  21. next(ret);
  22. returnnull;
  23. }
  24. //对gen.throw的处理
  25. functiononRejected(err){
  26. varret;
  27. try{
  28. ret=gen.throw(err);
  29. }catch(e){
  30. returnreject(e);
  31. }
  32. next(ret);
  33. }
  34. //实际处理的函数,会递归执行,直到ret.done状态为true
  35. functionnext(ret){
  36. //如果生成器的状态done为true,就resolve(ret.value),返回结果
  37. if(ret.done)returnresolve(ret.value);
  38. //否则,将gen的结果value封装成Promise
  39. varvalue=toPromise.call(ctx,ret.value);
  40. //判断value是否Promise,如果是就返回then
  41. if(value&&isPromise(value))returnvalue.then(onFulfilled,onRejected);
  42. //如果不是Promise,Rejected
  43. returnonRejected(newTypeError('Youmayonlyyieldafunction,promise,generator,array,orobject,'
  44. +'butthefollowingobjectwaspassed:"'+String(ret.value)+'"'));
  45. }
  46. });
  47. }

看到这里,我产生了一个疑问:Promise + then 也可以处理异步编程,为什么 co 的源码里要把 Promise + generator 结合起来呢,为什么要这样做?直到我搞懂了 co 的核心目的,它使 generator 和 yield 的语法更趋向于同步编程的写法,引用阮一峰的网络日志中的一句话就是:

异步编程的语法目标,就是怎样让它更像同步编程。

可以看一个 Promise + then 的例子:

  1. functiongetData(){
  2. returnnewPromise(function(resolve,reject){
  3. resolve(1111)
  4. })
  5. }
  6. getData().then(function(res){
  7. //处理第一个异步的结果
  8. console.log(res);
  9. //返回第二个异步
  10. returnPromise.resolve(2222)
  11. })
  12. .then(function(res){
  13. //处理第二个异步的结果
  14. console.log(res)
  15. })
  16. .catch(function(err){
  17. console.error(err);
  18. })

如果有多个异步处理就会需要写多少个 then 来处理异步之间可能存在的同步关系,从以上的代码可以看到 then 的处理是一层一层的嵌套。如果换成 co,在写法上更优雅也更符合日常同步编程的写法:

  1. co(function*(){
  2. try{
  3. varresult1=yieldPromise.resolve(1111)
  4. //处理第一个异步的结果
  5. console.log(result1);
  6. //返回第二个异步
  7. varresult2=yieldPromise.resolve(2222)
  8. //处理第二个异步的结果
  9. console.log(result2)
  10. }catch(err){
  11. console.error(err)
  12. }
  13. });

4.2 分析 next 函数

源码的 next 函数接收一个 gen.next() 返回的对象 ret 作为参数,形如{value: T, done: boolean},next 函数只有四行代码。

第一行:if (ret.done) return resolve(ret.value); 如果 ret.done 为 true,表明 gen 函数到了结束状态,就 resolve(ret.value),返回结果。

第二行:var value = toPromise.call(ctx, ret.value); 调用 toPromise.call(ctx, ret.value) 函数,toPromise 函数的作用是把 ret.value 转化成 Promise 类型,也就是用 Promise 包裹一层再 return 出去。

  1. functiontoPromise(obj){
  2. //如果obj不存在,直接返回obj
  3. if(!obj)returnobj;
  4. //如果obj是Promise类型,直接返回obj
  5. if(isPromise(obj))returnobj;
  6. //如果obj是生成器函数或遍历器对象,就递归调用co函数
  7. if(isGeneratorFunction(obj)||isGenerator(obj))returnco.call(this,obj);
  8. //如果obj是普通的函数类型,转换成Promise类型函数再返回
  9. if('function'==typeofobj)returnthunkToPromise.call(this,obj);
  10. //如果obj是一个数组,转换成Promise数组再返回
  11. if(Array.isArray(obj))returnarrayToPromise.call(this,obj);
  12. //如果obj是一个对象,转换成Promise对象再返回
  13. if(isObject(obj))returnobjectToPromise.call(this,obj);
  14. //其他情况直接返回
  15. returnobj;
  16. }

第三行:if (value && isPromise(value)) return value.then(onFulfilled, onRejected); 如果 value 是 Promise 类型,调用 onFulfilled 或 onRejected,实际上是递归调用了 next 函数本身,直到 done 状态为 true 或 throw error。

第四行:return onRejected(…) 如果不是 Promise,直接 Rejected。

5. 实践

虽然解读了 co 的核心代码,看起来像是懂了,实际上很容易遗忘。为了加深理解,结合上面的 co 源码和自己的思路动手实现一个简略版的 co。

5.1 模拟请求

  1. functionrequest(){
  2. returnnewPromise((resolve)=>{
  3. setTimeout(()=>{
  4. resolve({data:'request'});
  5. },1000);
  6. });
  7. }
  8. //用yield获取request的值
  9. function*getData(){
  10. yieldrequest()
  11. }
  12. varg=getData()
  13. var{value,done}=g.next()
  14. //间隔1s后打印{data:"request"}
  15. value.then(res=>console.log(res))

5.2 模拟实现简版 co

核心实现:

1)函数传参

2)generator.next 自动执行

  1. functionco(gen){
  2. //1.传参
  3. varctx=this;
  4. constargs=Array.prototype.slice.call(arguments,1);
  5. gengen=gen.apply(ctx,args);
  6. returnnewPromise(function(resolve,reject){
  7. //2.自动执行next
  8. onFulfilled()
  9. functiononFulfilled(res){
  10. varret=gen.next(res);
  11. next(ret);
  12. }
  13. functionnext(ret){
  14. if(ret.done)returnresolve(ret.value);
  15. //此处只处理ret.value是Promise对象的情况,其他类型简略版没处理
  16. varpromise=ret.value;
  17. //自动执行
  18. promise&&promise.then(onFulfilled);
  19. }
  20. })
  21. }
  22. //执行
  23. co(function*getData(){
  24. varresult=yieldrequest();
  25. //1s后打印{data:"request"}
  26. console.log(result)
  27. })

6. 感想

学习一个新的东西(generator)花费的时间远远大于单纯阅读源码的时间,因为需要了解它产生的背景,语法,解决的问题以及一些应用场景,这样在阅读源码的时候才知道它为什么要这样写。

读完源码,我们会发现,其实 co 就是一个自动执行 next() 的函数,而且到最后我们会发现 co 的写法和我们日常使用的 async/await 的写法非常相像,因此也不难理解【async/await 实际上是对 generator 封装的一个语法糖】这句话了。

  1. //co写法
  2. co(function*getData(){
  3. varresult=yieldrequest();
  4. //1s后打印{data:"request"}
  5. console.log(result)
  6. })
  7. //asyncawait写法
  8. (asyncfunctiongetData(){
  9. varresult=awaitrequest();
  10. //1s后打印{data:"request"}
  11. console.log(result)
  12. })()

不得不说,阅读源码的确是一个开阔视野的好方法。

原文地址:https://mp.weixin.qq.com/s/XhP_A3PVxhHHxMmbyl2Diw

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

快网idc优惠网 建站教程 面试官问 async、await 函数原理是在问什么? https://www.kuaiidc.com/89775.html

相关文章

发表评论
暂无评论