async与await的关系以及用法

148 阅读6分钟

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函数优点

  1. 内置执行器。generator函数执行必须依靠执行器,二async函数自带执行器,所以,async函数执行和普通函数一样,只要一行

    var result = asyncReadFile();
    
  2. 更好的语义,async和await比起星号和yield,语义更清晰,async表示函数中有异步操作,await表示紧跟后面的表达式需要等待结果。

  3. 更广的适用范围。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')
  1. 首先输出宏任务里的 script start
  2. 之后宏任务队列加入一个任务 console.log("setTimeout")
  3. 执行async1()函数,执行到第一个await()后面完成.所以输出async1 start
  4. 因为要执行async2()函数返回,所以输出 async2
  5. 宏任务async()返回一个promise对象,之后执行下面任务
  6. 执行new promise,输出promise1
  7. .then获得undefined,加入微任务中
  8. 输出 srcipt end
  9. 微任务队列第一个微任务是 step5返回的promise对象,输出async1 end
  10. 微任务队列第二个微任务是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

知识点

  1. promise优先于settimeout,因为微任务大于宏任务,setTimeout在最后执行
  2. promise一旦被定义,就会立即执行
  3. promise的reject()和resolve()是异步执行的回调,所以rsolve()会被放到回调队列中,在主函数执行完后和在setTimeout前调用.
  4. await执行完,会让出线程,async标记的函数会返回一个promise对象.

难点

出错的点在于async1 endpromise2后输出

在函数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前几天面试遇到这个题了,盲答一波,据说对了.