写在前面:
本人前端小白,刚刚接触前端的异步概念,若有写的不对的地方,评论区直接开喷即可。
众所周知,作为一个Java(jdk1.8)出身的辣鸡后端程序员,jdk1.8代码中本来是没有async和await这件事的,我一度认为它们是前端专有的关键字。排除掉线程,java代码基本是顺序执行的,这种日子直到我遇到了最新版的C#,一切都结束了......
新版C#中,支持开发人员将一个函数声明为异步的。么的办法,回来理解一下前端的async和await,以es6为例:
async // asynchronous(译:异步)的简写
await // async wait 的简写
async通常和await配套使用,async用于声明一个函数是异步的,await放在被声明为async的函数内部,用于等待await后面的结果,同时block住整个async的函数(此处暂不考虑awiat的对象是不是promise对象,先情景理解,后面再讲)。async和await的用法如下(ref:ES6之async和await - 简书 (jianshu.com)):
function doubleAfter2seconds(num) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2 * num)
}, 2000);
})
}
async function testResult () {
console.log('内部调用前')
let result = await doubleAfter2seconds(30);
console.log(result);
console.log('内部调用后')
}
console.log('外部调用前')
testResult();
console.log('外部调用后')
第一个函数function doubleAfter2seconds(num)是一个正常的顺序执行函数,该函数仅返回一个promise对象,先不纠结什么是promise对象,就理解为返回一个对象就行,这个对象会计算 2 * 参数num 的结果,并且等待2000ms后才得到计算结果。
第二个函数async function testResult()是一个异步函数。假如仅声明一个函数是async,而不用await,这其实和一般顺序函数是没有区别的。同理,仅在一句代码中加await,而不将对应函数声明成async的,也和顺序函数没区别,但es6语法中规定单用await没有意义,故await无法单独出现。上面的例子中async和await配套使用了。
这里await的作用,顾名思义就是等待doubleAfter2seconds(30)的返回结果,这里double函数内部模拟复杂逻辑耗时2000ms,故let result = 60(其实这里的60应该是一个promise对象,还是暂不考虑promise) 需要2000ms后才接受到结果60。await会block住对应的async函数,将异步函数放到浏览器队列中(js本身是单线程的,而c#是真正的多线程),让浏览器栈处理,主线程继续走主逻辑,主逻辑完成后,js对队列进行轮询,有结果则取回渲染,没有结果则重新放到队尾。js将自己一块内存共享给浏览器,用于接收异步的返回结果。轮询就是轮询这块共享内存。
所以输出结果:首先输出“外部调用前”,然后进入异步函数async testResult(),正常输出第一句“内部调用前”,紧接着result开始await结果60,整个testResult()函数被block住,交给浏览器运行。同时,主线程接着往下走,打印“外部调用后”。2000ms过后,result接收到结果60,testResult()继续往下走,打印出result:60,最后打印“内部调用后”。
结果:
// 外部调用前
// 内部调用前
// 外部调用后
// 60
// 内部调用后
回头来看一下promise的事情。let result 接收的其实不是60这个数字,而是一个promise对象,这个promise对象里面resolve的值是60
new Promise((resolve, reject) => {})
async/await是Promise的语法糖 <==> async和await是处理then链的语法糖,
这两句话是一样的。
语法糖:为了写法好看,async/await 像糖一样甜,但本质还是then链那一套。
then: (ref:js中then()的用法_dxyzhbb的博客-优快云博客_js then)
then()方法是异步执行
意思是:就是当.then()前的方法执行完后,再执行then()内部的程序,这样就避免了数据没获取到的问题,语法如下:
promise.then(onCompleted, onRejected); // 对应上面promise的resolve和reject
其中,onCompleted是成功时运行的函数,onRejected是拒绝时运行的函数,onCompleted是必须的,onRejected是可选的。
举例:
navigator.mediaDevices.getUserMedia(constrains).then(
function (stream) {
video.srcObject=stream;
}
)
可以理解成navigator.mediaDevices.getUserMedia(constrains) 返回一个参数stream,且返回成功了,然后执行function功能。
Then链的写法:(ref:【学习笔记】深入理解async/await - 唐吉sir - 博客园 (cnblogs.com))
/**
* 假设一个业务,分多个步骤完成,每个步骤都是异步的
* 而且后一步依赖于上一个步骤的结果
* 我们仍然用setTimeout来模拟异步操作
*/
function takeLongTime(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n);
});
}
function step1(n) {
console.log(`step1 : ${n}`);
return takeLongTime(n);
}
function step2(n) {
console.log(`step2 : ${n}`);
return takeLongTime(n);
}
function step3(n) {
console.log(`step3 : ${n}`);
return takeLongTime(n);
}
function doIt(){
console.time('doIt');
let time1 = 300;
step1(time1)
.then((time2) => step2(time2))
.then((time3) => step3(time3))
.then((result) => {
console.log(`result is ${result}`);
console.timeEnd("doIt");
})
}
doIt();
es6箭头函数:
// 参数=>表达式(函数体) ,箭头函数
x => x * x
// 等价于(也是一种语法糖)
function (x) {
return x * x;
}
async/await的写法:
async function doIt() {
console.time('doIt');
let time1 = 300;
let time2 = await step1(time1);//将Promise对象resolve(n+200)的值赋给time2
let time3 = await step1(time2);
let result = await step1(time3);
console.log(`result is ${result}`);
console.timeEnd('doIt');
}
doIt();
后者就是前者的语法糖写法,本质并无区别。