关于JS中async与await的理解

基础知识点:

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 endpromiseResolveValue,才输出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对象thencatchfinally方法
    • 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 方法之间的所有转换规则和转换方法,以后再遇到不管多复杂的写法,包括这三种方法混杂在一起写的代码,我们都可以轻松进行转换为一致的写法后进行分析了。

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值