当前位置: 首页 > 科技观察

面试官问async和await函数的原理是什么?

时间:2023-03-20 10:20:07 科技观察

1。前言这周看了co的源码。我对co比较陌生,从来没有了解和使用过。所以在看源码之前,希望对co是什么,解决什么问题有个大概的了解。2.首先要了解co,请阅读co的GitHub。README是这样介绍它的:Node.js和浏览器的基于生成器的控制流优势,使用promises,让您以一种不错的方式编写非阻塞代码。看得有点糊涂Forced,查了一些资料,大多说co是用来自动执行generator函数的。Generator是ES6提供的异步编程解决方案。它最大的特点是可以控制函数的执行。2.1关于generator提到异步编程,我们很容易想到promise、async和await。有什么不同?先看看JS异步编程的进化史:callback->promise->generator->async+awaitJS异步编程再看看它们的语法差异:CallbackPromiseGeneratorasync+await+Promiseajax(url,()=>{})Promise((resolve,reject)=>{resolve()}).then()function*gen(){yield1}asyncgetData(){awaitfetchData()}关于generator的学习就不详细写了在本文中。需要了解它的概念和语法。3、学习目标经过简单的学习,我大概了解了联产的背景,因为generator函数不会自动执行,需要手动调用它的next()函数。co的作用是自动执行generator的next()函数直到完成。直到状态变为真。所以我这段时间的学习目标:1)解读co源码,理解它是如何实现generator自动执行的2)手工实现co4的简化版。co源码解读co源码地址:https://github.com/tj/co4.1整体架构从README可以看到co的使用方法:co(function*(){varresult=yieldPromise.resolve(true);returnresult;}).then(function(value){console.log(value);},function(err){console.error(err.stack);});从代码中,我们可以看到它接收了一个生成器函数并返回了一个Promise。这部分对应的源码如下。functionco(gen){varctx=this;//获取参数varargs=slice.call(arguments,1);//返回一个PromisereturnnewPromise(function(resolve,reject){//将ctx和参数传给gen函数if(typeofgen==='function')gengen=gen.apply(ctx,args);//判断gen.next是否为函数,如果不是直接resolve(gen)if(!gen||typeofgen.next!=='function')returnresolve(gen);//先执行nexttonFulfilled();//其实是执行gen.next函数获取gen函数的值onFulfilled(res){varret;try{ret=gen.next(res);}catch(e){returnreject(e);}next(ret);returnull;}//gen.throw函数的处理onRejected(err){varret;try{ret=gen.throw(err);}catch(e){returnreject(e);}next(ret);}//实际处理函数会递归执行,直到ret.done的状态为真functionnext(ret){//如果生成器done的状态为真,则解析(ret.value),返回结果if(ret.done)returnresolve(ret.value);//否则将gen的结果值封装到Promise中varvalue=toPromise.call(ctx,ret.value);//判断value是否为Promise,如果是则返回thenif(value&&isPromise(value))returnvalue.then(onFulfilled,onRejected);//如果不是Promise,则拒绝returnonRejected(newTypeError('Youmayoonlyyieldafunction,promise,generator,array,orobject,'+'butthefollowingobjectwaspassed:"'+String(ret.value)+'"'));}});}看到这里,我有一个疑问:Promise+then也可以处理异步编程,为什么要结合Promise+generator,你为什么要这样做?直到理解了co的核心用途,才使得generator和yield的语法更倾向于同步编程。引用阮一峰博客的一句话是:异步编程的语法目标是如何让它更像同步编程,可以看一个Promise+then的例子:functiongetData(){returnnewPromise(function(resolve,reject){resolve(1111)})}getData().then(function(res){//处理第一个异步结果console.log(res);//返回第二个异步returnPromise.resolve(2222)}).then(function(res){//处理第二个异步结果console.log(res)}).catch(function(err){console.error(err);})如果有多个异步进程,then需要写入多少个处理异步进程之间可能存在的同步关系。从上面的代码我们可以看出,then的处理是一层一层的嵌套。如果换成co,写法更优雅,更符合日常同步编程的写法:co(function*(){try{varresult1=yieldPromise.resolve(1111)//处理第一个异步结果console.log(result1);//返回第二个异步varresult2=yieldPromise.resolve(2222)//处理第二个异步结果console.log(result2)}catch(err){console.error(err)}});4.2分析nextfunction源码的next函数接收gen.next()返回的一个对象ret作为参数,形式为{value:T,done:boolean},next函数只有四行代码。第一行:if(ret.done)returnresolve(ret.value);如果ret.done为真,说明gen函数已经到达结束状态,然后resolve(ret.value),返回结果。第二行:varvalue=toPromise.call(ctx,ret.value);调用toPromise.call(ctx,ret.value)函数,toPromise函数的作用是将ret.value转为Promise类型,也就是用Promise包裹一层再返回出去。functiontoPromise(obj){//如果obj不存在,直接returnobjif(!obj)returnobj;//如果obj是Promise类型,直接returnobjif(isPromise(obj))returnobj;//如果obj是一个生成器函数或遍历生成器对象,递归调用co函数functionandreturnif('function'==typeofobj)returnthunkToPromise.call(this,obj);//如果obj是数组,将其转为Promise数组并返回if(Array.isArray(obj))returnarrayToPromise.call(this,obj);//如果obj是一个对象,将其转化为Promise对象并返回if(isObject(obj))returnobjectToPromise.call(this,obj);//其他情况直接returnreturnobj;}第三种行:if(value&&isPromise(value))returnvalue.then(onFulfilled,onRejected);如果value是Promise类型,调用onFulfilled或onRejected实际上会递归调用next函数本身,直到完成状态为真或抛出错误。第四行:returnonRejected(...)如果不是Promise,直接Rejected。5.练习co的核心代码虽然已经解释过了,看似听懂了,其实很容易忘记。为了加深理解,结合上面的co源码和自己的想法,实现一个co的简化版。5.1模拟请求函数request(){returnnewPromise((resolve)=>{setTimeout(()=>{resolve({data:'request'});},1000);});}//获取request的值withyieldfunction*getData(){yieldrequest()}varg=getData()var{value,done}=g.next()//打印{data:"request"}value.then(res=>console.log1s后interval(res))5.2模拟实现简化版co的核心实现:1)函数参数传递2)generator.next自动执行functionco(gen){//1。传递参数varctx=this;constargs=Array.prototype.slice.call(arguments,1);gengen=gen.apply(ctx,args);returnnewPromise(function(resolve,reject){//2.自动执行nexttonFulfilled()functiononFulfilled(res){varret=gen.next(res);next(ret);}functionnext(ret){if(ret.done)returnresolve(ret.value);//这里只处理ret.value为一个Promise对象,其他类型的简化版不处理varpromise=ret.value;//自动执行promise&&promise.then(onFulfilled);}})}//执行co(function*getData(){varresult=yieldrequest();//1秒后打印{data:"request"}console.log(result)})6.感觉学习一个新东西(generator)比单纯看源码要花的时间要多得多,因为你需要了解它的背景、语法、需要解决的问题、一些应用场景,这样只有看了才知道源码为什么这么写。看完源码我们会发现co其实是一个自动执行next()的函数,最后我们会发现co的写法和我们日常使用的async/await很像,所以不难理解【async/awaitawait其实是一个生成器封装的语法糖】这句话。//协写方法co(function*getData(){varresult=yieldrequest();//1s后打印{data:"request"}console.log(result)})//asyncawait写法(asyncfunctiongetData(){varresult=awaitrequest();//1s后,打印{data:"request"}console.log(result)})()不得不说,阅读源码确实是开阔眼界的好方法。