由于学习Node.js写后台代码,那么必不可少要学习一下async/await。顺其自然的就找到了阮一峰大师的ES6文档,翻阅了其async这一模块内容来学习。主要来说说await吧
Await 了解过程
- 初步了解:
- 来源:阮一峰版本ES6入门文档
- async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
- await命令类似于Promise对象then的语法糖
- 深究:
- 辅助查询:Job queue 和 event loop
- await 命令后面若不是Promise对象,但是await命令后的函数体内执行了promise会是什么样。
基于上面的三点的内容让我对Await有了最初的了解,当然还是不太清楚的。通过JS-Bin边打印,边测试。还不是很清楚async/await的好处,虽然文档明明白白的写着以同步代码的方式来写异步代码。没有去理解async异步,await等待的意思。就开始写Node.js了。刚好我用到的数据库处理模块是sequelize,基于bluebird的promise对象写的。这就为我之后苦苦寻找await到底是如果判断等待结束标志埋下了种子。
Part.1
导火索是类似于下面的代码
const blueBird= require("bluebird");// bluebird对象
async function test(){
await blue();
await native();
console.log("END");
}
// 原生Promise对象调用方法
function native(){
new Promise(r=>{r(2)})
.then(r=>{
console.log(`native:${r}`);
})
}
// Bluebird对象调用方法
function blue(){
new blueBird((r)=>{r(2);})
.then(r=>{
console.log(`blue:${r}`);
});
}
test();
上面代码运行后的结果是: native : 2 END blue : 2
首先我的疑惑是为什么bluebird的promise的回调会在最后执行呢?
这里需要查询的知识点有bluebird的异步是如何实现的,事件循环与任务队列两部分内容,await属于什么任务队列。
经查询后,
- bluebird的回调操作属于宏任务,网上查阅非官网资料说是基于setTimeout来进行设计的(有兴趣可以查找更多的资料进行考证)。
- async/await本质上是基于Promise的一种封装,属于微任务队列。
基于以上两点,能理解了为什么blue:2一定在最后执行,因为宏任务队列的执行顺序在微任务之后。
Part.2
现在令我疑惑的是为什么native:2会在END之前呢?
await 后面的表达式返回的不是Promise对象,是undefined。那么按道理native的主线程代码走完以后,就应该await等待结束了。根据我已经获得的知识,不能解释。按照我目前的理解是,
test开始 => blue执行 => blue中的bluebird对象回调加入宏任务队列 ,执行结束 => blue返回undefined,blue结束,第一个await等待结束 => native执行 => native中的promise对象的回调加入微任务队列 => native返回undefined,native结束,第二个await等待结束 => 打印"END" => test结束 => 执行微队列中的promise回调,打印得到"native : 2" => 执行宏任务队列中的bluebird回调,打印得到"blue:2"。
为什么!?native:2 会在END之前打印呢? 不解决真的令我心里有结。在与技术交流群中的兄弟们讨论了后,终于豁然开朗了。令我这么迷惑最根本的原因是关于await有一句关键的话我没有看到:
红色箭头的话什么意思?意思就是await后面如果跟的不是Promise,那么await也会将其包装成Promise来处理,加入promise队列。赶紧用代码来验证一下。
async function test(){
new Promise(resolve=>{resolve(2)})
.then(r=>{
console.log(`test is ${r}`);
}).then(r=>{
console.log(`test is 3`);
});
await native();
console.log("END");
}
function native(){
new Promise(resolve=>{
resolve(2);
}).then(r=>{
console.log(`native is ${r}`);
})
}
test();
和我猜想的一样。处理的顺序应该是
主线程:
test开始 => 第一个promise对象执行完毕,回调加入微任务队列 => native开始执行 => native执行结束,回调加入微队列 => await后面的native返回undefined,await将其包装成promise,并加入微队列,await等待未结束
第一次事件循环:
- 将微队列中第一个任务拿出,打印出"test is 2" ,并将第一个promise的第二个then操作产生一个微任务,加入微任务队列最后
- 将微任务队列中第二个任务拿出,打印" native is 2"
- 将微任务队列中的第三个任务拿出,await等待结束,打印"END",异步方法test执行结束
- 将最后一个微任务拿出,打印" test is 3"
这样解释就通了,为了理清这些内容,向群里的朋友们请教,网上也查了写资料,但是发现大多数的帖子的内容都是大同小异,所以将自己的一些发现写做博客,希望可以给和我一样遇到这个问题而不解的朋友做个参考。
以上内容,如有不对之处,请帮忙纠正,谢谢!