基础知识点:
1. async返回的是一个promise函数
async function testAsync() {
return 'hello async';
}
const result = testAsync();
console.log(result)
输出:
Promise {: “hello async”}
proto: Promise
[[PromiseState]]: “fulfilled”
[[PromiseResult]]: “hello async”
相当于
function testAsync() {
return new Promise(resolve){
resolve('hello async')
}
}
const result = testAsync();
console.log(result) // 打印结果与上面一致
2. Promise.resolve(x) <==> new Promise(resolve => {resolve(x)})
所以以上例子又可以写成:
function testAsync() {
return Promise.resolve('hello async');
}
const result = testAsync();
console.log(result) // 打印结果与上面一致
3. 如果一个函数返回 promise对象,那么这个函数自动拥有一个then方法
所以我们可以这样输出hello async
function testAsync() {
return Promise.resolve('hello async');
}
testAsync().then(v => {
console.log(v)
});
4. then方法返回的还是一个promise对象
以上代码如果在浏览器控制台执行,它的输出结果是
hello async
Promise {: undefined}proto: Promise*[[PromiseState]]: “fulfilled”[[PromiseResult]]*: undefined
这个promise对象,就是then方法执行后的返回值。
5. await等待的是一个表达式
所以有几种情形:
-
表达式是直接量:
async function testAsync() { console.log('async start'); await '20'; console.log('after await'); return 'hello async' } var result = testAsync(); console.log(result)
输出如下:
async start
VM342:9 Promise {}
VM342:4 after await -
表达式是一个普通函数
async function testAsync() { console.log('async start'); await doSomeThing(); console.log('after await'); return 'hello async' } function doSomeThing() { return '20'; } var result = testAsync(); console.log(result) // 输出结果与上面相同
可以看到,输出结果与上面直接量的输出结果相同,我们可以稍作修改
async function testAsync() { console.log('async start'); await doSomeThing(); console.log('after await'); return 'hello async' } function doSomeThing() { console.log('20'); } var result = testAsync(); console.log(result)
async start
VM356:9 20
VM356:13 Promise {}
VM356:4 after await -
表达式是一个
async
函数async function testAsync() { console.log('async start'); await doSomeThing(); console.log('after await'); return 'hello async' } async function doSomeThing() { console.log('20'); } var result = testAsync(); console.log(result) // 输出与上例相同
可以看到,无论await等待的表达式是什么,在表达式执行完成后,程序都将跳出当前
async
函数的执行上下文,继续执行外层执行上下文中的同步代码,执行完成后,才会回来继续执行该表达式后面的代码;
6. 同一轮事件循环中, 异步微任务会比异步宏任务先执行
所以, promise.then
方法中的代码会优先于setTimeout
中的执行:
setTimeout(function(){
console.log('timeout');
},0)
new Promise(resolve => {
resolve('promiseResolveValue')
}).then(v=>{
console.log(v);
});
输出:
promiseResolveValue
timeout
7. await 后面的表达式之后的代码是异步微任务
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async1();
async function async2() {
console.log('20');
}
setTimeout(function(){
console.log('timeout');
},0);
new Promise(resolve => {
resolve('promiseResolveValue')
}).then(v=>{
console.log(v);
});
console.log('haha');
async1 start
20
haha
async1 end
promiseResolveValue
timeout可以看到,在执行完
async2
函数后,继续去执行外面的同步代码里,输出了haha
,同步代码执行结束后会先执行异步微任务,再执行异步宏任务。所以先输出了async1 end
和promiseResolveValue
,才输出timeout
,又由于异步微任务是一个队列,遵循先进先出(FIFO)原则,所以先输出了async1 end
再输出promiseResolveValue
.
我们调整一下两个微任务的加入顺序:
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
new Promise(resolve => {
resolve('promiseResolveValue')
}).then(v=>{
console.log(v);
});
async1();
async function async2() {
console.log('20');
}
setTimeout(function(){
console.log('timeout');
},0);
console.log('haha');
async1 start
20
haha
promiseResolveValue
async1 end
timeout其它的没有变化,只是这次
promiseResolveValue
先于async1 end
输出了,因为new Promise...
先于async1
执行,所以它的then
方法先于async2()
后面的代码加入微任务队列,随意先输出了。
8. 异步宏任务和异步微任务都有哪些:
- 异步宏任务
setTimeout
setInterval
setImmediate
requestAnimationFrame
I/O
- 异步微任务
promise对象
的then
,catch
,finally
方法await xxx
表达式后面的代码MutationObserver
9. 旧版本浏览器await表达式后面的代码是阻塞不是微任务
在chrome 73
以前版本的浏览器中,await
按照规范设计为阻塞,当await
后面的表达式计算出结果后,await
将让出线程,以便程序继续执行外层执行上下文中的同步代码,这和现在是一样的,不一样的是,同步代码执行完成后,线程总会先回到await
后面阻塞的地方继续执行完剩余代码,才会去执行异步微任务,所以在较低版本浏览器中,第7部分的两个示例将会输出同样的结果,都是先输出async1 end
再输出promiseResolveValue
, 这可能会造成一些旧的代码的问题,不过可以借助于babel
这样的工具将他们转换成一致行为的代码。
高级延伸
我们再来看前面的一段代码:
async function testAsync() {
console.log('async start');
await doSomeThing();
console.log('after await');
return 'hello async'
}
async function doSomeThing() {
console.log('20');
}
var result = testAsync();
console.log(result)
在这段代码中,testAsync
方法是一个async
方法,那么按照基础知识点1, 我们可以将其改写为:
function testAsync() {
return new Promise(resolve=> {
console.log('async start');
await doSomeThing();
console.log('after await');
resolve('hello async');
});
}
async function doSomeThing() {
console.log('20');
}
var result = testAsync();
console.log(result)
当然,这段代码是不能运行的,因为await
关键字只能用于async
函数中,又由于await
后面的表达式计算结果其实也是一个promise
,那么我们可以继续改写:
function testAsync() {
return new Promise(resolve=> {
console.log('async start');
doSomeThing();
console.log('after await');
resolve('hello async');
});
}
function doSomeThing() {
return new Promise(resolve=>{
console.log('20');
resolve();
});
}
var result = testAsync();
console.log(result)
这次输出的结果就和我们之前说的一样了。但是,还不够,基于基础知识点7,我们还可以继续改写:
function testAsync() {
return new Promise(resolve=> {
console.log('async start');
return new Promise(resolve=>{
console.log('20');
resolve();
}).then(v => {
console.log('after await');
resolve('hello async');
});
});
}
var result = testAsync();
console.log(result)
又根据 基础知识点2,我们可以继续改写:
function testAsync() {
return Promise.resolve(a());
}
function a () {
console.log('async start');
return Promise.resolve(b()).then(v => {
console.log('after waite');
return 'hello async'
});
}
function b () {
console.log('20');
}
var result = testAsync();
console.log(result)
我们去掉中间函数,使用立即调用函数代替:
function testAsync() {
return Promise.resolve((function(){
console.log('async start');
return Promise.resolve((function(){
console.log('20');
})()).then(v => {
console.log('after waite');
return 'hello async'
});
})());
}
var result = testAsync();
console.log(result)
到这一步,我们就熟悉了从new Promise()
,到Promise.resolve()
,再到async/await
方法之间的所有转换规则和转换方法,以后再遇到不管多复杂的写法,包括这三种方法混杂在一起写的代码,我们都可以轻松进行转换为一致的写法后进行分析了。