10分钟解读 JavaScript Async/Await

2,468 阅读5分钟

在很长一段时间里,JavaScript 开发人员不得不依赖回调来处理异步代码。因此,我们中的许多人都经历过回调地狱,当面对这样的函数时,我们会感到无赖

不过好在JavaScript提供了,.then()的一种回复方式,目前也有很多人正在用他

现在,随着 Async/Await 的最新增加,编写 JavaScript 代码将会变得更加优雅!

什么是 Async/Await?

Async/Await 是一个期待已久的 JavaScript 特性,它使得使用异步函数变得更加愉快和更容易理解。它建立在 Promises 之上,并与所有现有的基于 promise 的 api 兼容

这个名字来自 async 和 await ——这两个关键词可以帮助我们清理异步代码:

Async-声明一个异步函数(Async function someName (){ ... })

  1. 自动地将一个普通的函数转换成一个承诺
  2. 当调用异步函数时,当异步完成时,然后返回的是异步内容
  3. 在异步函数中允许使用await

Await-暂停 async 函数的执行

  1. 当被放置在一个Promise前时, 强制代码的其余部分等待,直到承诺完成并返回结果
  2. 只对 Promises 起作用,对 callback 不起作用
  3. 只能在里面使用async函数中使用

通过一个简单的例子来分析

假设我们想从服务器获取一些 JSON 文件。我们将编写一个使用 axios 库的函数,并向 tutorialzine.com/misc/files/… 发送一个 HTTP GET 请求。我们必须等待服务器响应,所以这个 HTTP 请求自然是异步的

下面我们可以看到同一个函数实现了两个。首先是使用 Promises,然后是第二个使用 Async/Await

// Promise
function getJSON(){
    // 为了使函数阻塞,我们手动创建一个承诺.
    return new Promise( function(resolve) {
        axios.get('https://tutorialzine.com/misc/files/example.json')
            .then( function(json) {
                // 在.then中获得返回的json数据
                // 我们使用resolve返回结果
                resolve(json);
            });
    });

}

// Async/Await
// async关键字将自动创建一个新的Promise并返回它.
async function getJSONAsync(){

    // await关键字使我们不必编写then().
    let json = await axios.get('https://tutorialzine.com/misc/files/example.json');

    // GET请求的结果在json变量中可用.
    // 获取数据就像在一个常规的同步函数中一样
    return json;
}

很明显,Async/Await 版本的代码更短,更容易阅读。除了使用的语法之外,这两个函数完全相同——它们都返回 Promises 并使用 axios 的 JSON 响应解析。我们可以这样调用我们的 async 函数:

async返回本身就是一个Promise

getJSONAsync().then( function(result) {
    // Do something with result.
});

Async/Await 会使承诺过时吗?

不,一点也不。 在使用 Async/Await 时,我们仍然在引擎盖下使用 Promises。 从长远来看,充分理解Promise实际上会对你有所帮助,因此强烈推荐你这么做

甚至在一些情况下 Async/Await 也不能解决问题,我们不得不回到 Promises 寻求帮助。其中一种情况是,我们需要进行多个独立的异步调用,并等待它们全部完成

如果我们尝试使用 async 和 await 来实现这个功能,将会发生以下情况:

async function getABC() {
  let A = await getValueA(); // getValueA 需要2秒
  let B = await getValueB(); // getValueB 需要4秒
  let C = await getValueC(); // getValueC 需要3秒
  return A*B*C;
}

每个await等待都将等待await前一个返回结果。因为我们一次只调用一个函数,整个函数从开始到结束(2 + 4 + 3)需要9秒

这不是最佳解决方案,因为三个变量 a、 b 和 c 并不相互依赖。换句话说,在得到 b 之前,我们不需要知道 a 的值。我们可以在同一时间得到它们,减少几秒钟的等待时间

在同一时间发送所有请求。这将确保我们在继续之前仍然拥有所有的结果,但是异步调用将并行请求,而不是一个接一个地请求

async function getABC() {
  // Promise.all()允许我们同时发送多个请求,类型是个数组

  let results = await Promise.all([ getValueA, getValueB, getValueC ]); 

  return results.reduce((total,value) => total * value);
}

这样一来,函数运行的时间就会少得多。getValueA 和 getValueC 调用在 getValueB 结束时已经完成,将有效地将执行时间减少到最慢的请求的时间(getValueB-4秒) ,而不是总和

Async/Await 中的错误处理

Async/Await 的另一个优点是,它允许我们在一个很好在 try/catch 块中捕捉任何意外的错误。只需要像这样包装下Await:

async function doSomethingAsync(){
    try {
        // 这里异步可能会发送失败
        let result = await someAsyncCall();
    }
    catch(error) {
        // 如果失败了,通过catch捕捉到错误
    }  
}

Catch 子句将处理等待的异步调用或者我们在 try 块中编写的任何其他失败代码所引发的错误

如果情况需要,我们还可以在执行 async 函数时捕获错误。因为所有的异步函数都返回 Promises,我们可以简单地包含一个。在调用 catch ()事件处理程序时。

async function doSomethingAsync(){
    // This async call may fail.
    let result = await someAsyncCall();
    return result;  
}

// 后面更用catch捕获错误
doSomethingAsync().
    .then(successHandler)
    .catch(errorHandler);

总结

随着 Async/Await 的加入,JavaScript 语言在代码可读性和易用性方面取得了巨大的飞跃。编写类似于常规同步函数的异步代码的能力将受到初学者、 JavaScript 开发者和资深编码者的青睐

文章属于翻译,作者:羊先生,
英文原文