async&await
async函数是generator函数的语法糖
以一个generator函数,一次读取两个文件
var fs = require('fs');
var readFile = function (fileName){
return new Promise(function (resolve, reject){
fs.readFile(fileName, function(error, data){
if (error) reject(error);
resolve(data);
});
});
};
var gen = function* (){
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
携程async函数
var asyncReadFile = async function (){
var f1 = await readFile('/etc/fstab');
var f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
async函数就是将generator函数的星号(*)替换成async,将yield替换成await,仅此。
async函数优点
-
内置执行器。generator函数执行必须依靠执行器,二async函数自带执行器,所以,async函数执行和普通函数一样,只要一行
var result = asyncReadFile(); -
更好的语义,async和await比起星号和yield,语义更清晰,async表示函数中有异步操作,await表示紧跟后面的表达式需要等待结果。
-
更广的适用范围。yield命令后面只能是thunk或者promise,erasync函数的await命令后面可以跟promise对象和原始类型的值(数值,字符串等,这是等同于同步操作
async函数的实现
async函数就是将generator函数和自动执行器,包装在一个函数里
async function fn(args){
// ...
}
// 等同于
function fn(args){
return spawn(function*() {
// ...
});
}
所有async函数都写成上面的第二种形式,其中spawn函数的自动执行器.
function spawn(genF) {
return new Promise(function(resolve, reject) {
var gen = genF();
function step(nextF) {
try {
var next = nextF();
} catch(e) {
return reject(e);
}
if(next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}
async函数用法
同generator函数一样,async函数返回一个promise对象,可以使用then方法添加回调函数,当函数执行的时候,一旦遇到await就会先返回,等到触发异步的操作完成,再接着执行函数体内后面的语句.
async function getStockPriceByName(name) {
var symbol = await getStockSymbol(name);
var stockPrice = await getStockPrice(symbol);
return stockPrice;
}
getStockPriceByName('goog').then(function (result){
console.log(result);
});
2
async起什么作用
async function testAsync() {
return "hello async";
}
const result = testAsync();
console.log(result);
输出一个promise对象
所以async函数返回一个promise对象 如果在函数中return一个直接变量,async会把直接量通过promise.resolve()封装成promise对象.
- promise.resolve(x)可以看作new promise(resolve=>resolve(x))简写,用于快速封装成promise实例
async函数返回一个promise对象,所以在最外层不能用await获取返回值情况下,应该用then()链来处理promise对象
testAsync().then(v => {
console.log(v); // 输出 hello async
});
await等待
一般认为await在等一个async函数完成.按照语法,await等待一个表达式,这个表达式计算结果是promise对象或者其他值
因为async函数返回一个promise对象,所以await可以用于等待一个async函数返回值,可以说await在等async函数
await等到了要等的,之后
await等到了要等的东西,一个promise对象,或者其他值,之后
- 如果等到的不是一个promise对象,那么await表达式的运算结果就是他等到的东西.
- 如果等到一个promise,他会阻塞后面的代码,等着promise对象的resolve,得到resolve的值,作为await表达式的运算结果
上面的阻塞就是await必须用在async函数中的原因,async函数调用不会造成阻塞,它内部所有的阻塞被封装在一个promise对象中异步执行
async&await干了啥
上面说了async会将其后面的函数的返回值封装成proise对象,而await会等待这个promise完成,并将其resolve的结果返回出来.
现在举例,用setTimeout模拟耗时的异步操作,先看不用async/await的
function takeLongTime() {
return new Promise(resolve => {
setTimeout(() => resolve("long_time_value"), 1000);
});
}
takeLongTime().then(v => {
console.log("got", v);
});
function takeLongTime(){
return new Promise(resolve=>{
setTimeout(()=>resolve("long_time_value"),1000);
})
}
async function test(){
const v=await takeLongTime();
console.log(v);
}
test();
takeLongTime()没有声明为async,实际上takeLongTime()本身就是返回的promise对象,加不加async有一点区别,加了async后,外面的promise对象不是return那一个
async/await的优势在于处理then链
单一的promise链不能发现async/await的优势,但事实要处理多个promise组成的then链的时候,就有了
假设一个业务,分多个步骤完成,每一个步骤都是异步的,且依赖于上一个步骤的结果,仍用setTimeout模拟异步
/**
* 传入参数 n,表示这个函数执行的时间(毫秒)
* 执行的结果是 n + 200,这个值将用于下一步骤
*/
function takeLongTime(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n);
});
}
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(n) {
console.log(`step2 with ${n}`);
return takeLongTime(n);
}
function step3(n) {
console.log(`step3 with ${n}`);
return takeLongTime(n);
}
用promise方式实现三个步骤处理
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then(time2 => step2(time2))
.then(time3 => step3(time3))
.then(result => {
console.log(`result is ${result}`);
console.timeEnd("doIt");
});
}
doIt();
// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms
如果用async/await来实现呢
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time2);
const result = await step3(time3);
console.log(`result is ${result}`);
console.timeEnd("doIt");
}
doIt();
后记
下列代码输出顺序
async function async1(){
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2(){
console.log('async2')
}
console.log('script start')
setTimeout(function(){
console.log('setTimeout')
},0)
async1();
new Promise(function(resolve){
console.log('promise1')
resolve();
}).then(function(){
console.log('promise2')
})
console.log('script end')
- 首先输出宏任务里的 script start
- 之后宏任务队列加入一个任务 console.log("setTimeout")
- 执行async1()函数,执行到第一个await()后面完成.所以输出async1 start
- 因为要执行async2()函数返回,所以输出 async2
- 宏任务async()返回一个promise对象,之后执行下面任务
- 执行new promise,输出promise1
- .then获得undefined,加入微任务中
- 输出 srcipt end
- 微任务队列第一个微任务是 step5返回的promise对象,输出async1 end
- 微任务队列第二个微任务是step7返回的promise对象,输出promise2
以上是我自己没有看答案想象的
所以我认为的输出是
script start
async1 start
async2
promise1
script end
async1 end
promise2
看了答案后,发现忘记宏任务,微任务的顺序也有一点不对,正确答案如下
script start
async1 start
async2
promise1
script end
promise2
async1 end
setTimeout
知识点
- promise优先于settimeout,因为微任务大于宏任务,setTimeout在最后执行
- promise一旦被定义,就会立即执行
- promise的reject()和resolve()是异步执行的回调,所以rsolve()会被放到回调队列中,在主函数执行完后和在setTimeout前调用.
- await执行完,会让出线程,async标记的函数会返回一个promise对象.
难点
出错的点在于async1 end在promise2后输出
在函数async1中,执行primise(async2是async标记的函数,所以默认返回promise对象,会发现resolve(),然后放到微任务队列)
接着执行下方的new promise()中的resolve()输出promise,再回来输出async1 end.
async1函数可以理解成如下方式(便于理解)
async function async1(){
console.log('async1 start')
async2().then( _ => {
console.log( 'async1 end ')
})
}
这样就相当于这里的async2造了一个promise,并且首先进入了微任务队列
reference:www.cnblogs.com/shaozhu520/…
ps.ps前几天面试遇到这个题了,盲答一波,据说对了.