基础系列-async&await

async 函数与 await 命令的使用

解决的问题:

减少多层嵌套,可以进行同步的写法。

使Promise链式调用可控,即中间promise出现问题后能够跳出继续向下调用,能够及时处理错误。

优点:

(1)内置执行器。async 函数的执行与普通函数一模一样,只要一行。

(2)更好的语义。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。

(3)更广的适用性。async 函数的 await 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。

用法:

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

async function getStockPriceByName(name) { // 获取股票报价
  var symbol = await getStockSymbol(name);
  var stockPrice = await getStockPrice(symbol);
  return stockPrice;
} // async关键字表明该函数内部有异步操作

getStockPriceByName('goog').then(function (result){
  console.log(result);
}); // 调用该函数时会立即返回一个Promise对象

指定50毫秒后输出“hello world”

function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value)
}

asyncPrint('hello world', 50);

注意点

(1) await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try…catch 代码块中

await 最后返回的结果是 resolve ,无法处理 reject 情况。

如果await关键字后面的Promise对象中没有执行resolve方法,就会导致Promise一直处在pending状态,无法执行then方法,因此await后面的代码不会执行。

错误处理:

function somethingThatReturnsAPromise() {
    return new Promise.reject(1)
}

// 错误处理
// 写法一
async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

// 写法二
async function myFunction() {
  await somethingThatReturnsAPromise().catch(function (err){
    console.log(err);
  });
}

// 写法三
function errPromise() {
    return new Promise((resolve, reject) => {
        reject(1)
    }).catch(err => {
        console.log('promise err: ', err)
    })
}

async function myFunction() {
    const res = await errPromise()
    console.log('await result', res) // promise err: 1, await result: undefined 
}

易错情况:

// 无法处理 reject 情况
async function test() {
    await new Promise.reject(false)
    console.log('执行结束') // 报错,无法执行到console
}
test()
// 改正
async function test() {
  try {
    await Promise.reject(false)
  } catch(err) {
     console.log('执行结束: ', err)
  }
}
test() // 执行结束: false

(2) await 命令只能用在 async 函数之中,如果用在普通函数,就会报错。

但是,await 可以用于等待一个 async 函数的返回值,await 不仅等 promise 对象,也可以等任意表达式的结果。

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  // 报错
  docs.forEach(function (doc) { // 在function前加async关键字也有问题,db.post(doc)是并法的,而不是继发执行,应当采用for循环,而不是forEach
    await db.post(doc); 
  });
}

// await 返回普通函数
function foo() {
  return false
}
async function c() {
  const res = await foo()
  console.log('res: ', res)
}
c() // res: false

// await 返回普通包装函数
function a() {
  return b()
}
function b() {
  return new Promise((resolve, reject) => {
    resolve(1) // 注意 当前结果只处理了 resolve 情况
  })
}
async function c() {
  const res = await a()
  console.log('res: ', res)
}
c()

// 改进
function a() {
  return b().catch(err => {
    console.log('出错: ', err)
  })
}
function b() {
  return new Promise((resolve, reject) => {
    resolve(1) // 如果是 reject(1) =》 输出结果是 出错: 1 res: undefined
  })
}

async function c() {
  const res = await a()
  console.log('res: ', res)
}

c() // resolve 输出结果 res: 1

(3) 如果 async 函数返回非 Promise 对象,那么返回值会被封装成 Promise 对象。

// async 包装普通函数为 promise
async function bar() {
  return 'test'
}

bar().then(res => {
  console.log('res: ', res)
}) // res: test

(4) 使用promise.all方法解决多个请求并发执行

在定义 promise 时函数就开始执行

// 写法一
async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = await Promise.all(promises);
  console.log(results);
}

// 写法二
async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = [];
  for (let promise of promises) {
    results.push(await promise);
  }
  console.log(results);
}

使用Generators + Promise 实现 Await

Generator(看似同步的异步代码)与 Promise(可靠性和可组合性)组合起来。

function run(gen) {
    var args = [].slice.call( arguments, 1), it;

    // 在当前的上下文环境中初始化generator
    it = gen.apply( this, args );

    // 为generator的完成返回一个promise
    return Promise.resolve()
        .then( function handleNext(value){
            // 运行至下一个让出的值
            var next = it.next( value );

            return (function handleResult(next){
                // generator已经完成运行了?
                if (next.done) {
                    return next.value;
                }
                // 否则继续执行
                else {
                    return Promise.resolve( next.value )
                        .then(
                            // 在成功的情况下继续异步循环,将解析的值送回generator
                            handleNext,

                            // 如果`value`是一个拒绝的promise,就将错误传播回generator自己的错误处理
                            function handleErr(err) {
                                return Promise.resolve(
                                    it.throw( err )
                                )
                                    .then( handleResult );
                            }
                        );
                	}
        	})(next);
    	}
    );
}

// 使用
// 创建一个Generator
function *foo() {
    // axios返回一个promise
	var r1 = yield axios( "http://some.url.1" );
	var r2 = yield axios( "http://some.url.2" );

	var r3 = yield axios(
		"http://some.url.3/?v=" + r1 + "," + r2
	);

	console.log( r3 );
}

// 使用刚才定义的`run(..)`工具
run( foo );

代码为《你不知道的JavaScript(中卷)》中的示例代码

参考链接

阮一峰·async 函数的含义和用法:http://www.ruanyifeng.com/blog/2015/05/async.html

理解 JavaScript 的 async/await:https://segmentfault.com/a/1190000007535316