async/await 原理及简单实现
栏目: JavaScript · 发布时间: 5年前
内容简介:解决函数回调经历了几个阶段, Promise 对象, Generator 函数到async函数。async函数目前是解决函数回调的最佳方案。很多语言目前都实现了async,包括Python ,java spring,go等。async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。async /await 需要在function外部书写async,在内部需要等待执行的函数前书写await即可
解决函数回调经历了几个阶段, Promise 对象, Generator 函数到async函数。async函数目前是解决函数回调的最佳方案。很多语言目前都实现了async,包括Python ,java spring,go等。
async await 的用法
async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。
function getNum(num){ return new Promise((resolve, reject) => { setTimeout(() => { resolve(num+1) }, 1000) }) } const func = async ()=>{ const f1 = await getNum(1) const f2 = await getNum(f1) console.log(f2) // 输出3 } func() 复制代码
async /await 需要在function外部书写async,在内部需要等待执行的函数前书写await即可
深入理解
理解async函数需要先理解Generator函数,因为 async函数是Generator函数的语法糖
。
Generator[ˈdʒɛnəˌretɚ]函数-生成器
Generator是ES6标准引入的新的数据类型。Generator可以理解为一个状态机,内部封装了很多状态,同时返回一个迭代器Iterator对象。可以通过这个迭代器遍历相关的值及状态。 Generator的显著特点是可以多次返回,每次的返回值作为迭代器的一部分保存下来,可以被我们显式调用。
Generator函数的声明
一般的函数使用function声明,return作为回调(没有遇到return,在结尾调用return undefined),只可以回调一次。而Generator函数使用function*定义,除了return,还使用yield返回多次。
function* foo(x) { yield x + 1; yield x + 2; return x + 3; } const result = foo(0) // foo {<suspended>} result.next(); // {value: 1, done: false} result.next(); // {value: 2, done: false} result.next(); // {value: 3, done: true} result.next(); //{value: undefined, done: true} 复制代码
在chrome浏览器中这个例子里,我们可以看到,在执行foo函数后返回了一个
Generator函数的实例。它具有状态值suspended和closed,suspended代表暂停,closed则为结束。但是这个状态是无法捕获的,我们只能通过Generator函数的提供的方法获取当前的状态。
在执行next方法后,顺序执行了yield的返回值。返回值有value和done两个状态。value为返回值,可以是任意类型。done的状态为false和true,true即为执行完毕。在执行完毕后再次调用返回{value: undefined, done: true} 注意:在遇到return的时候,所有剩下的yield不再执行,直接返回{ value: undefined, done: true }
Generator函数的方法
Generator函数提供了3个方法,next/return/throw
next方式是按步执行,每次返回一个值,同时也可以每次传入新的值作为计算
function* foo(x) { let a = yield x + 1; let b= yield a + 2; return x + 3; } const result = foo(0) // foo {<suspended>} result.next(1); // {value: 1, done: false} result.next(2); // {value: 2, done: false} result.next(3); // {value: 3, done: true} result.next(4); //{value: undefined, done: true} 复制代码
return则直接跳过所有步骤,直接返回 {value: undefined, done: true}
throw则根据函数中书写try catch返回catch中的内容,如果没有写try,则直接抛出异常
function* foo(x) { try{ yield x+1 yield x+2 yield x+3 yield x+4 }catch(e){ console.log('catch it') } } const result = foo(0) // foo {<suspended>} result.next(); // {value: 1, done: false} result.next(); // {value: 2, done: false} result.throw(); // catch it {value: undefined, done: true} result.next(); //{value: undefined, done: true} 复制代码
这里可以看到在执行throw之前,顺序的执行了状态,但是在遇到throw的时候,则直接走进catche并改变了状态。
这里还要注意一下,因为状态机是根据执行状态的步骤而执行,所以如果执行thow的时候,没有遇到try catch则会直接抛错 以下面两个为例
function* foo(x) { yield x+1 try{ yield x+2 }catch(e){ console.log('catch it') } } const result = foo(0) // foo {<suspended>} result.next(); // {value: 1, done: false} result.next(); // {value: 2, done: false} result.throw(); // catch it {value: undefined, done: true} result.next(); //{value: undefined, done: true} 复制代码
这个例子与之前的执行状态一样,因为在执行到throw的时候,已经执行到try语句,所以可以执行,而下面的例子则不一样
function* foo(x) { yield x+1 try{ yield x+2 }catch(e){ console.log('catch it') } } const result = foo(0) // foo {<suspended>} result.next(); // {value: 1, done: false} result.throw(); // Uncaught undefined result.next(); //{value: undefined, done: true} 复制代码
执行throw的时候,还没有进入到try语句,所以直接抛错,抛出undefined为throw未传参数,如果传入参数则显示为传入的参数。此状态与未写try的抛错状态一致。
遍历
Generator函数的返回值是一个带有状态的Generator实例。它可以被for of 调用,进行遍历,且只可被for of 调用。此时将返回他的所有状态
function* foo(x) { console.log('start') yield x+1 console.log('state 1') yield x+2 console.log('end') } const result = foo(0) // foo {<suspended>} for(let i of result){ console.log(i) } //start //1 //state 1 //2 //end result.next() //{value: undefined, done: true} 复制代码
调用for of方法后,在后台调用next(),当done属性为true的时候,循环退出。因此Generator函数的实例将顺序执行一遍,再次调用时,状态为已完成
状态的存储和改变
Generator函数中yield返回的值是可以被变量存储和改变的。
function* foo(x) { let a = yield x + 0; let b= yield a + 2; yield x; yield a yield b } const result = foo(0) result.next() // {value: 0, done: false} result.next(2) // {value: 4, done: false} result.next(3) // {value: 0, done: false} result.next(4) // {value: 2, done: false} result.next(5) // {value: 3, done: false} 复制代码
以上的执行结果中,我们可以看到,在第二步的时候,我们传入2这个参数,foo函数中的a的变量的值0被替换为2,并且在第4次迭代的时候,返回的是2。而第三次迭代的时候,传入的3参数,替换了b的值4,并在第5次迭代的时候返回了3。所以传入的参数,是替代上一次迭代的生成值。
yield* 委托
在Generator函数中,我们有时需要将多个迭代器的值合在一起,我们可以使用yield *的形式,将执行委托给另外一个Generator函数
function* foo1() { yield 1; yield 2; return "foo1 end"; } function* foo2() { yield 3; yield 4; } function* foo() { yield* foo1(); yield* foo2(); yield 5; } const result = foo(); console.log(iterator.next());// "{ value: 1, done: false }" console.log(iterator.next());// "{ value: 2, done: false }" console.log(iterator.next());// "{ value: 3, done: false }" console.log(iterator.next());// "{ value: 4, done: false }" console.log(iterator.next());// "{ value: 5, done: false }" console.log(iterator.next());// "{ value: undefined, done: true }" 复制代码
foo在执行的时候,首先委托给了foo1,等foo1执行完毕,再委托给foo2。但是我们发现,”foo1 end” 这一句并没有输出。 在整个Generator中,return只能有一次,在委托的时候,所有的yield*都是以函数表达式的形式出现。return的值是表达式的结果,在委托结束之前其内部都是暂停的,等待到表达式的结果的时候,将结果直接返回给foo。此时foo内部没有接收的变量,所以未打印。 如果我们希望捕获这个值,可以使用yield *foo()的方式进行获取。
实现一个简单的async/await
如上,我们掌握了Generator函数的使用方法。async/await语法糖就是使用Generator函数+自动执行器来运作的。 我们可以参考以下例子
// 定义了一个promise,用来模拟异步请求,作用是传入参数++ function getNum(num){ return new Promise((resolve, reject) => { setTimeout(() => { resolve(num+1) }, 1000) }) } //自动执行器,如果一个Generator函数没有执行完,则递归调用 function asyncFun(func){ var gen = func(); function next(data){ var result = gen.next(data); if (result.done) return result.value; result.value.then(function(data){ next(data); }); } next(); } // 所需要执行的Generator函数,内部的数据在执行完成一步的promise之后,再调用下一步 var func = function* (){ var f1 = yield getNum(1); var f2 = yield getNum(f1); console.log(f2) ; }; asyncFun(func); 复制代码
在执行的过程中,判断一个函数的promise是否完成,如果已经完成,将结果传入下一个函数,继续重复此步骤。
总结
async/await非常好理解,基本理解了Generator函数之后,几句话就可以描述清楚。这里没有过多的继续阐述Generator函数的内部执行逻辑及原理,如果有对此有深入理解的童鞋,欢迎补充说明。
以上所述就是小编给大家介绍的《async/await 原理及简单实现》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Docker实现原理之 - OverlayFS实现原理
- 微热山丘,探索 IoC、AOP 实现原理(二) AOP 实现原理
- 带你了解vue计算属性的实现原理以及vuex的实现原理
- Docker原理之 - CGroup实现原理
- AOP如何实现及实现原理
- webpack 实现 HMR 及其实现原理
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。