首页 > 编程笔记

JS async和await关键字的用法(非常详细)

JavaScript 中的 async/await 是继 Promise 之后在 ES2017 中新定义的关键字。

async 用于定义异步函数,await 用于获取异步函数的执行结果,它们在语法形式上对 Promise 进行了修改,使代码编写起来更像是同步式的,阅读起来更加直观,但是底层还是基于 Promise 实现的。

定义异步函数

异步函数是使用 async 关键字定义的函数,除了函数名前边需要加上 async 之外,在定义上与普通的函数没有什么区别,不过它的返回值会包装成 Promise 对象。

例如定义一个异步函数,代码如下:
async function getTitle(){
    return "标题"
}
如果直接调用这个函数并打印返回值,则会输出:

Promise{<fulfilled >: "标题"}

如果想要访问返回值,则需要像 Promise 一样使用 then(),代码如下:
getTitle().then(title=>console.log(title));
除此之外,在异步函数中可以使用 await 关键字获取其他 Promise 或异步函数的执行结果。

使用 await

JS await 关键字的作用相当于 then(),Promise 完成之后的值会作为 await 关键字的返回值,可以把它保存到变量中再进行后续操作,代码如下:
const promise=new Promise(resolve=>setTimeout(resolve,3*1000,"done"));
async function logResult(){
    const result=await promise;
    console.log(result);
}
logResult();
代码中定义了一个 promise,在 3s 后打印出"done"字符串,之后在一个 async 函数 logResult() 中,使用了 await 等待 promise 的执行结果,并打印出来,代码的最后直接调用了 logResult() 这个 async 函数,它没有返回值,所以不需要在后边使用 then()。 运行代码并等待 3s 后,控制台就会打印出"done"。

这里的 await 和后边的代码相当于 then() 中的回调函数,类似下例,代码如下:
promise.then(result=>console.log(result));
只不过使用 await 这种方式更符合同步代码的风格。

需要注意的是,await 只能在异步函数中使用,类似于必须先有 Promise 才能有 then(),如果忘记写 async,则程序会抛出异常。

再来看一个例子,之前使用 fetch() 获取博客文章列表的代码,如果使用 async/await 则可以使用下方示例的形式,代码如下:
async function getPosts(){
    const res=await fetch("/api/posts");
    const posts=await res.json();
    return posts;
}
getPosts().then(posts=>console.log(posts));
代码最后同样会打印出获取的文章列表数组,不过这里可以看到,之前使用了两个 then() 分别获取 res 对象和 posts 数组,而这里使用 await 则更像是同步的代码,且两个 await 是按顺序执行的。

也就是说第1个 await 会等待 fetch() 的返回结果,在得到结果之后,第2个 await 才会执行,如果后边有更多的 await 关键字,则它们都会等待前一个执行完毕之后才会执行。

再看《JS Promise用法详解》一文中(靠后的一部分内容)顺序执行 Promise 的例子,这里同样使用之前定义的3个 promise,改成使用 await 的形式顺序执行,代码如下:
async function execPromises(){
    await promise1;
    await promise2;
    const value3=await promise3;
    return value3;
}
execPromises().then(value=>console.log(value));
这里最后打印出的结果同样也是 promise3 的返回值:1。

这种方式就比使用 then() 的方式清晰了很多。

不过,要想使 await 同时开始执行所有的 promise 或 async函数,可以借助 Promise.all() 实现,例如使用代码:
await Promise.all([promise, asyncFunc1(), asyncFunc2()])

处理异常

使用 async/await 处理异常的方式也相当直观,可以使用 try...catch 语句块包裹 await 语句,任何一条 await 抛出异常,都能够被 try 捕获,并在 catch 语句块中处理。

例如使用 fetch() 处理网络和请求错误,可以使用下方示例的形式,代码如下:
async function getPosts(){
    try{
        const res=await fetch("/api/posts");
        if(res.status>=400){
            throw res.status;
        }
        const posts=await res.json();
        return posts;
    }catch(error){
        if(error===404){
            return[];
        }else{
            console.log(error);
        }
    }
}
getPosts().then(posts=>console.log(posts));
如果响应状态码是 404,则 getPosts() 会返回空数组,其他状态码则直接使用 console.log() 打印了出来。

如果想分别捕获 fetch() 和 res.json() 的异常,则可以把它们分别放到两个 try...catch() 语句块中。同样地,也可以使用 finally() 执行一些收尾操作。

推荐阅读