async/await

async函数就是 Generator 函数的语法糖。

async 函数的优势

async对 Generator 函数的改进,体现在以下四点:

内置执行器

Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。

asyncReadFile();
1

上面的代码调用了asyncReadFile函数,然后它就会自动执行,输出最后结果。这完全不像 Generator 函数,需要调用next方法,或者用co模块,才能真正执行,得到最后结果。

更好的语义

asyncawait,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。

更广的适用性

co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即resolved的 Promise 对象)。

返回值是 Promise

async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。

基本用法

占位词说明

  • returnedPromiseasync函数返回的 Promise 实例对象
  • awaitedPromiseawait后面的 Promise 实例对象,即 awaitedPromise = new Promise(fn)
  • fn:创建 Promise 实例对象时传给构造函数的参数,形如function(resolve, reject) {...}

async 函数定义

// 函数声明
async function foo() {
  // ...
}

// 函数表达式
const foo = async function () {
  // ...
};

// 对象的方法
let obj = {
  async foo() {
    // ...
  }
};
obj.foo().then(...)

// Class 的方法
class Storage {
  constructor() {
    // ...
  }
  async getAvatar(name) {
    // ...
  }
}

// 箭头函数
const foo = async () => {
  // ...
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

async 函数的返回值

执行async函数,无论在什么情况下,一定会返回 Promise 实例对象。只是在不同的情况下,返回的 Promise 实例对象的状态改变的情况不一样。

async 函数 return 非 Promise 实例

若是async函数正常执行完,async函数里最终return的值(假设return的不是 Promise 实例),会成为添加到 Promise 实例对象的resolved回调函数的参数。

// 未抛错情况下

async function f1() {
  return 'hello world';
}
f1().then(
  data => console.log('data', data)
)
// "hello world"

async function f2() {
  // 什么都没有,即 return undefined
}
f2().then(
  data => console.log(data === undefined)
)
// true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

async 函数内抛错

若是async函数执行过程中抛出错误,则返回的 Promise 实例对象的状态将改变为rejected,可通过添加到 Promise 实例对象上的rejected回调函数处理错误。注意,这种情况下,此时必须做好调用async函数的错误处理,详情请见之后的错误处理一节。

// 抛错情况下

async function f1() {
  throw new Error('出错了');
}

f1().then(
  data => console.log('data', data)
).catch (
  err => console.log('err', err)
)
// err Error: 出错了
1
2
3
4
5
6
7
8
9
10
11
12

async 函数 return Promise 实例

若是async函数最终返回的是另一个 Promise 实例,则该 Promise 实例的状态将决定之前的 Promise 实例的状态。

async function f1() {
  return Promise.resolve('resolved')
}

f1().then(
  data => console.log('data', data)
).catch(
  err => console.log('err', err)
)
// data resolved

async function f2() {
  return Promise.reject('rejected')
}

f2().then(
  data => console.log('data', data)
).catch(
  err => console.log('err', err)
)
// err rejected
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

async 函数的返回时机

遇到首个 await 返回 Promise 实例

async函数执行的时候,一旦遇到首个await,会先将await命令之后的语句执行完,之后立即先返回一个 Promise 实例对象即returnedPromise,此时returnedPromise的状态仍是pending

注意这里各条语句的执行顺序,await后面的new Promise(fn)会先执行,其执行的过程中会执行fn函数,并返回一个 Promise 实例对象即promise,此时async函数获得返回值returnedPromise。等到异步操作完成即promise的状态resolved/rejected后,计算await promise的值,再接着执行await promise之后的语句,最终return的值将决定returnedPromise的状态改变。

// 执行顺序的示例
async function asyncFn() {
  console.log(1)

  let a = await new Promise(resolve => {
    console.log(2)

    setTimeout(() => {
      resolve(5)
    }, 200)
  })

  console.log('a', a)

  return 6
}

let pro = asyncFn()

console.log(3)

pro.then(
  data => console.log('data', data)
)

console.log(4)

// 执行结果
// 1
// 2
// 3
// 4
// a 5
// data 6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

遇到 return 返回 Promise 实例

若是在async函数过程中始终没有遇到await命令,则就一直不返回 Promise 实例,直到遇到return,或async函数执行结束(这是return undefined的特例)。

async function asyncFn() {
  console.log(1)

  return new Promise((resolve) => {
    console.log(2)

    resolve(4)
  })
}

const returnedPromise = asyncFn()

console.log(3)

returnedPromise.then(
  data => console.log(data)
)

// 执行结果
// 1
// 2
// 3
// 4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

await 命令

  • 正常情况下,await命令后面是一个 Promise 实例对象。如果不是,会被转成一个立即resolved的 Promise 实例对象
async function f() {
  return await 123;
}

f().then(v => console.log(v))
// 123
1
2
3
4
5
6
  • await命令后面的 Promise 实例对象如果变为rejected状态,则reject的参数会被catch方法的回调函数接收到。
async function f() {
  await Promise.reject('出错了');
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出错了
1
2
3
4
5
6
7
8
  • 只要一个await语句后面的 Promise 实例对象变为rejected状态,那么整个async函数都会中断执行
async function f() {
  await Promise.reject('出错了');
  await Promise.resolve('hello world'); // 不会执行
}
1
2
3
4

有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个await放在try...catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。

async function f() {
  try {
    await Promise.reject('出错了');
  } catch(e) {
  }
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// hello world
1
2
3
4
5
6
7
8
9
10
11
async function f() {
  await Promise.reject('出错了')
    .catch(e => console.log(e));
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// 出错了
// hello world
1
2
3
4
5
6
7
8
9
10

result = await promise 里 result 的值

  • await是运算符,await promise是表达式,该表达式返回的是promise异步操作的结果
  • promise的状态变为resolved时,result的值即为resolved时传递的参数值
  • promise的状态变为rejected
    • promise有错误处理,进行错误处理
      • 处理方式一try...catchpromise的状态改为rejected后直接catch处理,无法执行到赋值操作及await后面的代码,result保持原值
      • 处理方式二promise.then/catchresult的值为then/catch方法里的rejected函数return的值
    • promise无错误处理,则会结束async函数的执行,改变returnedPromise的状态为rejected
// promise 状态变为 rejected 且通过 try...catch 的方式进行错误处理
async function asyncFn() {
  let result
  try {
    let result = await new Promise((resolve, reject) => {
      reject('错误')
    })
    console.log('这里打印出来了吗?并没有!')
  } catch(err) {
    console.log('err: ' + err)
  }
  console.log('result: ' + result)

  // 执行结果
  // err: 错误
  // result: undefined
}

let pro = asyncFn()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// promise 状态变为 rejected 且通过 then/catch 的方式进行错误处理
async function asyncFn() {
  // 通过 catch 进行错误处理
  let result = await new Promise((resolve, reject) => {
    reject('错误')
  }).catch(err => {
    console.log('err: ' + err)
    return 'catch 返回的值'
  })

  console.log('result: ' + result)

  // 执行结果:
  // err: 错误
  // result: catch 返回的值
}

let pro = asyncFn()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

async 函数的常用场景

sleep 休眠

JavaScript 一直没有休眠的语法,但是借助await命令就可以让程序停顿指定的时间。下面给出了一个简化的sleep实现。

function sleep(interval) {
  return new Promise(resolve => {
    setTimeout(resolve, interval);
  })
}

// 用法
async function one2FiveInAsync() {
  for(let i = 1; i <= 5; i++) {
    console.log(i);
    await sleep(1000);
  }
}

one2FiveInAsync();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

使用注意点

  • await命令后面的 Promise 实例对象,最终的状态可能是rejected,所以必须做好错误处理
// async 函数内给 await 添加 try...catch
async function asyncFn() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log('err', err);
  }
}

// async 函数内给 await 后的 Promise 实例添加 then/catch
async function asyncFn() {
  await somethingThatReturnsAPromise().catch(
    err => console.log('err', err)
  );
}

// async 函数外,添加 catch
asyncFn.catch(
  err => console.log('err', err)
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  • await进行try..catch后再抛出错误跟不进行try..catch的效果是一样的
async function asyncFn() {
  try {
    await new Promise((resolve, reject) => {
      reject('error')
    })
  } catch (err) {
    return Promise.reject(err)
  }
}

asyncFn().catch(err => {
  console.log('err', err)
})
// err error
1
2
3
4
5
6
7
8
9
10
11
12
13
14

下面这种写法,跟上面的结果是一样的:

async function asyncFn() {
  await new Promise((resolve, reject) => {
    reject('error')
  })
}

asyncFn().catch(err => {
  console.log('err', err)
})
// err error
1
2
3
4
5
6
7
8
9
10
  • 多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发
let foo = await getFoo();
let bar = await getBar();

// 方式一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 方式二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
1
2
3
4
5
6
7
8
9
10
11
  • await命令只能用在async函数之中,如果用在普通函数,就会报错
async function dbFuc(db) {
  let docs = [{}, {}, {}];

  // 报错
  docs.forEach(function (doc) {
    await db.post(doc);
  });
}
1
2
3
4
5
6
7
8
  • async函数可以保留运行堆栈。
const a = () => {
  b().then(() => c());
};
1
2
3

上面代码中,函数a内部运行了一个异步任务b()。当b()运行的时候,函数a()不会中断,而是继续执行。等到b()运行结束,可能a()早就运行结束了,b()所在的上下文环境已经消失了。如果b()c()报错,错误堆栈将不包括a()

现在将这个例子改成async函数。

const a = async () => {
  await b();
  c();
};
1
2
3
4

上面代码中,b()运行的时候,a()是暂停执行,上下文环境都保存着。一旦b()c()报错,错误堆栈将包括a()

Reference

使用技巧