这一讲,我们介绍 JavaScript 中的异步,着重讲 async/await 语法。
异步编程

JavaScript 是单线程的编程语言,浏览器按照我们代码的顺序一行一行地执行程序。如果执行到一个耗时很长的任务,后面的任务就会被阻塞,拖延整个程序的执行。异步编程技术允许我们执行一个长时间任务时,程序不需要进行等待,而是继续执行后面的代码,直到任务完成之后再回来通知。这大大提高了程序的效率,尤其是对于输入输出密集的程序,比如文件读取,数据库查询,网络访问。
回调函数
在 JavaScript 中,函数也是对象,可以作为参数传入另一个函数,这也被称为回调函数。它曾经是 JavaScript 中实现异步函数的主要方式。下面是一个经典的例子,我们定义了一个 callback() 函数,并作为参数传给了 setTimeout() 定时器函数:
上面的程序会先输出 hello,然后再等待 1 秒后执行 callback 函数,输出“Hello, JavaScript!”,即使 setTimeout 函数在 console.log("hello") 之前。对 setTimeout 更详细的介绍请阅读MDN教程。
但如果异步任务数量很多时,这种方案容易出错,下面是使用回调函数维护 3 个异步任务的例子:
这种代码极难维护,也被称为 “回调地狱” (callback hell),现已被 Promise 方案取代。
Promise

Promise (承诺)是异步编程的现代解决方案,比回调函数方案更强大。它由社区最早提出和实现,ES6 将其写进语言标准并统一用法,原生提供了 Promise 对象。
Promise 对象有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。初始状态为 pending,最终状态由异步操作的结果决定。Promise 对象的状态改变,只有两种可能:从 pending 变为 fulfilled 和从 pending 变为 rejected。下面我们用 Promise 重写第一个示例:
由于 Promise 比较复杂,我们在后面的教程中再详细讲解。这一讲我们着重介绍在它之上建立的 async/await 语法。
async/await
async/await 是 Promise 的语法糖,让异步编程更易于理解和使用。
async 函数
我们可以在一个函数前面加上 async 关键字 ,将它变为异步函数,它的返回值会被自动包装为一个 Promise。与普通函数不同,异步函数不会阻塞程序的运行,让 JavaScript 引擎同时处理其他任务:执行其他脚本,处理事件等。
await 关键字
await 关键字只能在 async 函数内工作,一般情况下,await 后跟随一个 Promise 对象,作用是让 JavaScript 引擎等待直到 Promise 完成并返回结果。
注意,上面的代码会等待 1 秒后输出 Hello async,然后才是 hello await。
async/await 例子

下面,我们演示如何使用 async/await 语法来读取非常流行的无聊猿(BAYC)NFT的元数据。
- NFT 元数据是构成 NFT 内容的一组数据,通常以 JSON 格式保存在网络上。比如下面
url中的 ipfs 链接保存着id = 1的BAYC元数据,包括小图片网址和属性(嘴、头发、衣服等特征)。
- 你可以使用
fetch()函数来进行 HTTP 访问,获取网络数据。它会返回一个包装成Promise的 HTTP 响应,因此你需要使用await关键字来获取结果response。然后,你需要用json()方法获取 JSON 的内容,也就是元数据。
- 将上面的代码组合成一个
async函数getBaycMetadata:
习题
基于这一讲的例子,写一个根据用户输入的 tokenId(1~10000)来获取相应的无聊猿的元数据。
提示: 示例中url的最后一位的代表 tokenId,可以使用
{$tokenId}进行替换。
总结
这一讲我们介绍了 JavaScript 的异步编程,包括回调函数,Promise,以及重点讲的 async/await,并且利用它获取了无聊猿NFT的元数据。Promise 是现代 JavaScript 异步编程的基础。它避免了深度嵌套回调,使表达和理解异步操作序列变得更加容易。async/await 使得从一系列连续的异步函数调用中建立一个操作变得更加容易,避免了创建显式 Promise 链,并允许你像编写异步代码那样编写同步代码。