有一种特殊的语法可以以更舒适的方式处理promises,称为“async / await”。理解和使用起来非常简单。
async-await是promise和generator的语法糖。只是为了让我们书写代码时更加流畅,当然也增强了代码的可读性。简单来说:async-await 是建立在 promise机制之上的,并不能取代其地位。
Async functions
先从关键字async说起,它被放在一个函数前面,像下面这样:
async function f(){
return ‘Hello world’;
}
函数前面的“async”一词意味着一件简单的事:函数总是返回一个promise。即使函数实际返回非promise值,在函数定义前加上“async”关键字也会指示JavaScript自动将该值包装在已解析的promise中。
例如,下面的代码返回一个已解析的promise,其结果是1:
async function f() {
return 1;
}
f().then(alert);// 1
也可以直接返回一个promise,例如:
async function f () {
return Promise.resolve(1);
}
f().then(alert);// 1
因此,async确保函数返回一个promise,并在其中包含非promise。不仅如此。还有另一个关键字,await,它只能在async函数内部运行。
Await
语法:
// await只能放在async函数内部运行
let value = await promise;
await 后面可以跟任何的JS 表达式。虽然说 await 可以等很多类型的东西,但是它最主要的意图是用来等待 Promise 对象的状态被 resolved。如果await的是 promise对象会造成异步函数停止执行并且等待 promise 的解决,如果等的是正常的表达式则立即执行。
例如:
function sleep(second) {
return new Promise((resolve, reject) => {
setTimeout(() => { resolve(' enough sleep~'); }, second); }
)
}
function normalFunc() {
console.log('normalFunc');
}
async function awaitDemo() {
await normalFunc();
console.log('something, ~~');
let result = await sleep(2000);
console.log(result);// 两秒之后会被打印出来
}
awaitDemo();
注意:await字面意思是让JavaScript等到承诺结束,然后继续结果。这不会占用任何CPU资源,因为引擎可以同时执行其他任务:执行其他脚本,处理事件等。
这只是得到promise.then结果的更优雅的语法,易于读写。
不能await在常规功能中使用
如果我们尝试await在非异步函数中使用,则会出现语法错误:
function f () {
let promise = Promise.resolve(1);
let result = await promise; // Uncaught SyntaxError: await is only valid in async function
}
如果我们没有在函数之前写async,我们将得到此错误。如上所述,await只能在一个async function内部工作。
一个promise链式操作的例子,若用 ES5实现会有多层的回调,若用Promise 实现也需要多个then。一个是代码横向发展,另一个是纵向发展。
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`))
.then(response => response.json())
.then(githubUser => new Promise(function (resolve, reject) {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser);
}, 3000);
}))
// triggers after 3 seconds
.then(githubUser => alert(`Finished showing ${githubUser.name}`));
用async/await重写它:
1.将.then()替换为await
2.让函数变成async
async function showAvatar() {
// read our JSON
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
// read github user
let githubResponse = await fetch(`https://api.github.com/users/${user.name}`);
let githubUser = await githubResponse.json();
// show the avatar
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
// wait 3 seconds
await new Promise((resolve, reject) => setTimeout(resolve, 3000));
img.remove();
return githubUser;
}
showAvatar();
用了await之后代码相对来说更简洁和易读。
await 不能放在顶级作用域
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
可以将它包装成一个匿名的异步函数,如下所示:
(async () => {
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
...
})();
await接受thenables
就像promise.then,await也允许使用thenable对象(那些具有可调用的then方法的对象)。同样,第三方对象可能不是一个promise,但是promise的兼容性表示,如果它支持.then方法,那么它就能用于await。
例如,这里await接受了new Thenable(1)
class Thenable {
constructor(num) {
this.num = num
}
then(resolve, reject) {
alert(resolve) // function() {native code}
// 1000ms后将this.num*2作为resolve值
setTimeout(() => { resolve(this.num * 2), 1000 }) // (*)
}
}
async function f() {
// 等待1s,result变为2
let result = await new Thenable(1)
alert(result)
}
f()
如果await得到了一个带有then方法的非promise对象,它将会调用提供原生函数resolve、reject作为参数的方法,然后await一直等待,直到他们其中的一个被调用(在上面的例子它发生在(*)行)。
async方法
一个class方法同样能够使用async,只需要将async放在它之前就可以
就像这样:
class Waiter {
async wait() {
return await Promise.resolve(1)
}
}
new Waiter().wait().then(alert) // 1
含义相同:它确保返回的值是promise并启用await。
错误处理
如果一个promise正常resolve,那么await返回这个结果,但是在reject的情况下会抛出一个错误,就好像在那一行有一个throw语句一样。
async function f() {
await Promise.reject(new Error('whoops!'))
}
和这个一样
async function f() {
throw new Error("Whoops!");
}
在实际情况中,promise在reject抛出错误之前可能需要一段时间,所以await将会等待,然后才抛出一个错误。
我们可以使用try - catch语句捕获错误,就像在正常抛出中处理异常一样:
async function f() {
try {
let response = await fetch('http://no-such-url');
} catch (err) {
alert(err); // TypeError: failed to fetch
}
}
f();
如果出现错误,控件将跳转到该catch块。我们还可以包装多行:
async function f() {
try {
let response = await fetch('/no-user-here');
let user = await response.json();
} catch (err) {
// catches errors both in fetch and response.json
alert(err);
}
}
f();
如果我们没有try..catch,则异步函数调用生成的promise f()将被拒绝。我们可以附加.catch处理它:
async function f() {
let response = await fetch('http://no-such-url');
}
// f() becomes a rejected promise
f().catch(alert); // TypeError: failed to fetch // (*)
如果我们忘记添加.catch,我们就会得到一个未被处理的promise错误(能够在控制台里看到它),这时我们可以通过使用一个全局的事件处理器去捕获错误。
async/await 和 promise.then/catch 对比
当我们使用时async/await,我们很少需要.then,因为await为我们处理了等待。我们可以使用常规 try..catch 代替 .catch 。这通常(并非总是)更方便。
但是在代码的顶层,当我们在任何async函数之外时,我们在语法上无法使用await,这时候使用.then/catch来处理最终结果或捕获错误是正常的做法。
async / await 适用于 Promise.all
当我们需要等待多个 promise 时,我们可以将它们放进Promise.all然后await:
function sleep(second) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('request done! ' + new Date().getTime())
resolve();
}, second);
})
}
async function bugDemo() {
let p1 = sleep(1000);
let p2 = sleep(1000);
let p3 = sleep(1000);
await Promise.all([p1, p2, p3]);
console.log('over~');
}
bugDemo();
await in for循环(await必须在async函数的上下文中)
// 正常 for 循环
async function forDemo() {
let arr = [1,2,3,4,5];
for(leti = 0; i < arr.length; i ++) {
await arr[i];
}
}
forDemo();//正常输出
// 因为想要炫技把 for循环写成下面这样
async function forBugDemo() {
let arr = [1,2,3,4,5];
arr.forEach(item=>{
await item;
});
}
forBugDemo(); // Uncaught SyntaxError: await is only valid in async function
总结
放在一个函数前的async有两个作用:
1.使函数总是返回一个promise
2.允许在这其中使用await
promise前面的await关键字能够使JavaScript等待,直到promise处理结束。然后:
1.如果产生一个错误或者reject,异常就产生了,就像在那个地方调用了throw error一样。
2.否则,它会返回一个结果,我们可以将它分配给一个值