async/await
async
函数就是 Generator 函数的语法糖。
async 函数的优势
async
对 Generator 函数的改进,体现在以下四点:
内置执行器
Generator 函数的执行必须靠执行器,所以才有了co
模块,而async
函数自带执行器。也就是说,async
函数的执行,与普通函数一模一样,只要一行。
asyncReadFile();
上面的代码调用了asyncReadFile
函数,然后它就会自动执行,输出最后结果。这完全不像 Generator 函数,需要调用next
方法,或者用co
模块,才能真正执行,得到最后结果。
更好的语义
async
和await
,比起星号和yield
,语义更清楚了。async
表示函数里有异步操作,await
表示紧跟在后面的表达式需要等待结果。
更广的适用性
co
模块约定,yield
命令后面只能是 Thunk 函数或 Promise 对象,而async
函数的await
命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即resolved
的 Promise 对象)。
返回值是 Promise
async
函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then
方法指定下一步的操作。进一步说,async
函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await
命令就是内部then
命令的语法糖。
基本用法
占位词说明
returnedPromise
:async
函数返回的 Promise 实例对象awaitedPromise
:await
后面的 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 () => {
// ...
};
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
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: 出错了
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
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
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
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
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))
// 出错了
2
3
4
5
6
7
8
- 只要一个
await
语句后面的 Promise 实例对象变为rejected
状态,那么整个async
函数都会中断执行
async function f() {
await Promise.reject('出错了');
await Promise.resolve('hello world'); // 不会执行
}
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
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
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...catch
:promise
的状态改为rejected
后直接catch
处理,无法执行到赋值操作及await
后面的代码,result
保持原值 - 处理方式二
promise.then/catch
:result
的值为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()
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()
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();
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)
)
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
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
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;
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);
});
}
2
3
4
5
6
7
8
async
函数可以保留运行堆栈。
const a = () => {
b().then(() => c());
};
2
3
上面代码中,函数a
内部运行了一个异步任务b()
。当b()
运行的时候,函数a()
不会中断,而是继续执行。等到b()
运行结束,可能a()
早就运行结束了,b()
所在的上下文环境已经消失了。如果b()
或c()
报错,错误堆栈将不包括a()
。
现在将这个例子改成async
函数。
const a = async () => {
await b();
c();
};
2
3
4
上面代码中,b()
运行的时候,a()
是暂停执行,上下文环境都保存着。一旦b()
或c()
报错,错误堆栈将包括a()
。