Javascript 通常被称为异步语言。这意味着它不会像其他语言一样一直从上到下运行。一个完美的例子是:
- 您使用该
fetch
函数来调用 API。 - 当然,提取会触发(即消息会发送到 API),但 Javascript不会等待响应。
- Javascript 移到下一行,但其中包含 fetch 的变量仍未得到响应。
- 如果您尝试调用 fetch 变量,它可能有也可能没有您需要的数据,它的响应取决于您的互联网速度(以及 HTTP 协议的速度)。
因此,Javascript 需要一种方法来等待数据从 API 等服务返回,然后再继续下一行。为此,我们在 Javascript中使用了一堆异步关键字和函数。让我们看看其中的一些。
Fetch#
在我们开始之前,您可能值得熟悉fetch
. 这是一个可以从 HTTP URL 中获取网页或 API 的函数。如果您将 URL 放入其中,它将尝试检索该 URL 的内容。通常,我们fetch
在尝试从某处获取 JSON 数据时使用。我们通常使用 API 来做到这一点。
下面是一个 fetch 如何工作的例子。
let getData = fetch('/api/getData', { method: 'GET' }); // Returns Promise {pending} console.log(getData);
上面的行将使用GET方法从/api/getData获取数据。也可以使用其他标准 HTTP 请求,例如POST、PUT、PATCH、DELETE。
如前所述,当我们发送它时,Javascript 会立即转到下一行。这使得我们的变量变得无用。让我们看看如何避免这种情况。
异步函数#
解决等待问题的一种方法是使用异步函数。这些函数让我们fetch
在继续下一行之前等待 , 等函数的响应。我们使用一个名为await
等待获取行完成的关键字。
我们必须使用异步函数,因为在撰写本文时await仅适用于异步函数。未来的 Javascript 提案没有这个限制,但目前还没有得到广泛支持。
因此,我们的新代码如下所示:
let myAsync = async function() { let getData = await fetch('/api/getData', { method: 'GET' }); let response = await getData.json(); // Returns our expected data: console.log(response); } // Run the function myAsync();
所以现在当我们调用我们的 fetch 函数时,其余代码将不会运行,直到我们从 API 获得响应。此方法适用于任何返回promise的函数,即返回的内容fetch
,但 promise 到底是什么?
什么是promises?#
Promises 可以概括为不会立即完全执行的代码。API 调用或使用 HTTP 请求的代码是一个很好的例子,但是有超时的代码也可能希望返回一个承诺。我们可以使用new Promise轻松生成我们自己的承诺。这让我们必须返回一个函数作为承诺,然后可以与await结合使用。
promise 就像任何其他函数一样,我们可以使用两个额外的关键字,它们是函数中的参数:
- reject – 这让我们拒绝承诺,即如果发生错误。
- resolve – 这让我们解决了承诺,即代码现在已经完成,这是输出。
在我们使用普通函数的地方,在我们使用reject/resolve
return
的 promises 中。让我们尝试做出承诺:
let acceptableTimeout = 500; let badTimeout = 1000; function createAPromise() { return new Promise((resolve, reject) => { setTimeout(function() { reject('timeout expired') }, badTimeout); setTimeout(function() { resolve('timeout worked!') }, acceptableTimeout); }); } // Run the function, will initially return Promise {pending} console.log(createAPromise());
虽然这是一个简单的示例,但它展示了 promise 通常是如何工作的。函数createAPromise返回一个承诺,然后根据超时拒绝或解决。如果reject函数先运行,我们实际上会在 Javascript 中得到一个错误。否则,我们将从resolve中获取值。在上面的例子中,代码总是解析。
当然,现在我们需要能够等待我们的承诺,所以我们需要把它放在一个异步函数中并等待它:
(async function() { let letsWait = await createAPromise(); // Returns 'timeout worked!' console.log(letsWait); })();
我们的异步函数中的任何内容,以及该letsWait
行之后的内容,只有在我们的函数中超时完成后才会运行。然而,有时这种格式并不合适,我们无法用await
. 对于其他用例,我们有then/catch/finally。
Then/Catch/Finally#
Promises 通常被称为thennable。这意味着我们可以then()
对它们使用该功能。它遵循如下所示的格式:
Promise.then(resolve, reject);
所以我们有两个参数,一个在 promise 解决时处理,一个在它拒绝时处理。使用我们的createAPromise示例,我们可以在 then 中启动两个函数来处理这两种情况:
createAPromise().then(function(data) { // For resolving console.log('Everything worked!'); return `Message was: ${data}`; }, function(data) { // For rejecting console.log('Nothing is working.'); return `Message was: ${data}`; })
您会注意到,对于这两个函数,我们都有一个名为data的变量。其中包含resolve或reject中返回的任何内容。在这一点上,可能值得指出您可以使用任何 Javascript 类型解析或拒绝,因此如果您的承诺返回一个 JSON 对象,我们可以在then()
函数中使用它。
然后是thennable
我们可以将 then 函数链接在一起,如果我们的 then 函数之一返回一个 promise,甚至当它没有返回时。下一个 then 从上一个 then 获取数据,假设它成功解析。这意味着我们可以这样做:
myPromise().then(resolveFunctionA, rejectFunctionA).then(resolveFunctionB, rejectFunctionC) // ... etc
请记住,要使用最后一个 then 的数据,我们需要在那个 then 函数中使用return 。如果我们在前面的 then 中没有返回任何数据,那么下一个 then 将不会接收到任何数据,而我们上面使用的数据变量将是undefined
. 由于下一个 then 仅在最后一个成功解析时运行,finally
如果我们需要一个在then
其解析与否之后运行的函数,我们使用。这个函数只接受一个参数,但不从最后一个 then 中获取任何数据。我们可能会用它来注意到事件链最终结束了,无论是否有错误,即:
let didWeFinish = false; createAPromise().then( data => { return `Message was: ${data}` }, data => { return `Message was: ${data}` } ).finally(() => { didWeFinish = true; });
全部完成后,上面的代码将设置didWeFinish
为true。
Catch
最后(没有双关语意),我们可以链接catch
到我们的 then 序列,以捕获任何错误。假设我们运行then函数,并且我们期待一个对象,所以我们尝试这样操作它。我们会得到一个类型错误——然后我们可以捕获它以防止代码崩溃。
这是我们使用catch语句的代码:
let didWeFinish = false; createAPromise().then( data => { return `Message was: ${data}` }, data => { return `Message was: ${data}` } ).catch(() => { console.log('uh oh, we ran into an error'); }).finally(() => { didWeFinish = true; });
结论#
异步函数和管理 promises 是 Javascript 的关键部分,尤其是当您使用 API 时。了解它们的工作原理是学习 Javascript 的重要部分。我们希望您喜欢这篇文章。
您可以在此处找到更多Javascript 内容。