ES6 拾遗(7)

async函数

ES2017 标准引入了async函数, 使得异步操作变得更加方便. async函数是什么, 用一句话来说, 它就是Generator函数的语法糖

async函数返回一个Promise对象, 可以使用then方法添加回调函数. 当函数执行时, 一旦遇到await就会先返回Promise对象, 等到异步操作完成, 再接着执行函数体内后面的语句

function timeout(ms){

  return new Promise((resolve)=>{

    setTimeout(resolve,ms)

  })

}

 

async function asyncPrint(value,ms){

  await timeout(ms);

  console.log(value);

}

 

let theReturnOfAsync = asyncPrint("hello world",2000);

console.log(theReturnOfAsync);

在上面的例子中可以看到async函数返回了一个Promise对象, 而await命令后面是一个Promise对象. 如果不是, 会被转成一个立即resolve的Promise对象

async 函数返回一个Promise对象
async函数内部return语句返回的值, 会成为then方法回调函数的参数, async函数内部抛出的错误会导致返回的Promise对象变为reject状态, 抛出的错误对象会被catch方法回调函数接收到

Promise对象的状态变化

async函数返回的Promise对象必需等到内部所有await命令后面的Promise对象执行完毕才会发生状态改变, 除非遇到return语句或者抛出错误.
也就是说, 只有async函数内部的异步操作执行完, 才会执行then方法指定的回调函数

await命令

正常情况下, await命令后面是一个Promise对象. 如果不是, 会被转为一个立即resolve的Promise对象
只要一个await语句后面的Promise变为reject, 那么整个async函数都会被中断
有时, 我们希望即使前一个异步操作失败, 也不要中断后面的异步操作. 这时可以将第一个await放在try…catch结构里面, 这样不管这个异步操作是否成功, 第二个await都会执行

另一个方法是在await后面的Promise对象后直接添加一个catch方法, 处理前面的错误

async function asyncPrint(value,ms){

    await timeout(ms).catch(e => console.log(e));

    console.log(value);

}

请求的发生时机

一开始我想当然的认为ES6中的await后面的方法在触发之后, resolve之前, 会继续执行下一个await, 和java的feature.get()一样

如果第一个feature.get() 耗时 10秒, 第二个耗时11秒, 那么第二个feature会在第一个第一个feature结束后1秒结束

然而ES6中的await并不是这样, 下面的代码能说明这个问题

每个await方法耗时20秒, 第二个会在第一个结束20秒之后才结束,一共耗时40秒

function timeout(ms){

  return new Promise((resolve)=>{

    setTimeout(resolve,ms)

  })

}

 

async function asyncPrint(value){

  console.log("start"); // 0 second

  await timeout(20000); // 20 seconds

  console.log(value);

  await timeout(20000); // 40 seconds

  console.log("end")

}

 

let theReturnOfAsync = asyncPrint("hello world");

console.log(theReturnOfAsync);

稍微做一下改动可以得到一个带返回值的版本

function timeout(ms,value) {

  return new Promise(resolve => {

    setTimeout(resolve, ms, value);

  });

}

 

async function asyncPrint(value) {

  console.log("start"); // 0 second

  await timeout(2000,"test1").then((value)=>console.log(value)); // 2 seconds

  await timeout(2000,"test2").then((value)=>console.log(value)); // 4 seconds

  console.log("end");

}

 

let theReturnOfAsync = asyncPrint("hello world");

console.log(theReturnOfAsync);

使用Promise.all 方法可以保证同时出发多个Promise对象

function timeout(ms,value) {

  return new Promise(resolve => {

    setTimeout(resolve, ms, value);

  });

}

 

async function asyncPrint(value) {

  console.log("start"); // 0 second

  Promise.all([timeout(5000,"test1"), timeout(2000,"test2"), timeout(2000,"test3")]).then(function(values) {

    console.log(values);  // 5 seconds

  });

  console.log("end");

}

 

let theReturnOfAsync = asyncPrint("hello world");

console.log(theReturnOfAsync);

第二种同时触发Promise的写法, 需要注意的是
await 关键字后面如果是Promise对象, 就会造成阻塞, 例如
await getPromise() 就会造成阻塞, 因为getPromise()会返回一个Promise对象, 那么下一个await语句就会被迫等到第一个await返回之后才会被触发
下面的写法中因为先进行了三次调用, 然后await promise, 核心点在于, 同时出发3个请求, 然后对promise对象进行await

function timeout(ms,value) {

  return new Promise(resolve => {

    setTimeout(resolve, ms, value);

  });

}

 

async function asyncPrint(value) {

  console.log("start"); // 0 second

  let test1 = timeout(5000,"test1");

  let test2 = timeout(7000,"test2");

  let test3 = timeout(9000,"test3");

  

  let test1_return = await test1;

  // 5 seconds

  console.log(test1_return);

 

  let test2_return = await test2;

  // 7 seconds

  console.log(test2_return);

 

  let test3_return = await test3;

  // 9 seconds

  console.log(test3_return);

  

  console.log("end");

}

 

let theReturnOfAsync = asyncPrint("hello world");

console.log(theReturnOfAsync);