异步 JavaScript 简单指南:回调、Promises 和 async/await

2025-05-27

异步 JavaScript 简单指南:回调、Promises 和 async/await

JavaScript 中的异步编程是编写更好的 JavaScript 所需要掌握的基本概念之一。

今天,我们将学习异步 JavaScript,并结合一些实际示例和一些实际案例。通过本文,您将了解以下功能:

  • 异步回调
  • 承诺
  • 异步/等待

目录

1 - 同步 vs 异步

在讨论异步编程之前,我们先来讨论一下同步编程

例如,

let greetings = "Hello World.";

let sum = 1 + 10;

console.log(greetings);

console.log("Greetings came first.")

console.log(sum);
Enter fullscreen mode Exit fullscreen mode

您将按照此顺序获得输出。

Hello World.
Greetings came first.
11
Enter fullscreen mode Exit fullscreen mode

这是同步的。请注意,当每个操作发生时,其他任何事情都不会发生。

JavaScript 的核心是单线程:当执行一个代码块时,不会执行其他代码块。

异步编程则不同。简单来说,当 JavaScript 识别出异步任务时,它会继续执行代码,同时等待这些异步任务完成。

异步编程通常与并行化相关,并行化是并行执行独立任务的艺术。

这怎么可能呢?

相信我,我们在不知不觉中就以异步的方式做事。

让我们举一个现实生活中的例子来更好地理解。

现实生活中的例子:咖啡店

杰克来到咖啡店,径直走向第一位服务员。(主线)

  • 杰克:你好。可以给我一杯咖啡吗?(第一个异步任务)
  • 首席服务员:当然可以。您还需要点别的吗?
  • 杰克:等咖啡煮好,真是小菜一碟。(第二个异步任务)
  • 首席服务员:当然可以。(开始准备咖啡)
  • 首席乘务员:还有什么吗?
  • 杰克:不。
  • 首席乘务员:请给我 5 美元。
  • 杰克:付了钱就坐。
  • 第一位服务员:开始为下一位顾客服务。
  • 杰克:等待的时候开始查看 Twitter。
  • 第二位服务员:这是您的蛋糕。(第二个异步任务调用返回)
  • 杰克:谢谢
  • 第一位服务员:这是您的咖啡。(第一个异步任务调用返回)
  • 杰克:嘿,谢谢!把他的东西拿走吧。

简单来说,当您等待别人为您做事时,您可以做其他任务或要求别人为您做更多的事情。

现在您已经清楚地了解了异步编程的工作原理,让我们看看如何使用以下内容编写异步代码:

  • 异步回调
  • 承诺
  • async/await语法。

2 – 异步回调:一旦我完成,我会回调!

回调函数是在调用函数(高阶函数)时作为参数传递的函数,该函数将在后台开始执行任务。
当此后台任务完成运行时,它会调用回调函数来通知您更改。

function callBackTech(callback, tech) {
    console.log("Calling callBackTech!");
    if (callback) {
        callback(tech);
    }
    console.log("Calling callBackTech finished!");
}

function logTechDetails(tech) {
    if (tech) {
        console.log("The technology used is: " + tech);
    }
}

callBackTech(logTechDetails, "HTML5");
Enter fullscreen mode Exit fullscreen mode

输出

输出回调

正如您在这里看到的,代码每行之后都会执行一次:这是同步执行回调函数的示例。

如果你经常使用 JavaScript 编写代码,你可能已经在不知不觉中使用过回调函数了。例如:

  • array.map(callback)
  • array.forEach(callback)
  • array.filter(callback)
let fruits = ['orange', 'lemon', 'banana']

fruits.forEach(function logFruit(fruit){
    console.log(fruit);
});
Enter fullscreen mode Exit fullscreen mode

输出

orange
lemon
banana
Enter fullscreen mode Exit fullscreen mode

但回调也可以异步执行,这仅仅意味着回调比高阶函数执行得晚。

让我们重写我们的例子,使用setTimeout()函数来注册一个异步调用的回调。

function callBackTech(callback, tech) {
    console.log("Calling callBackTech!");
    if (callback) {
        setTimeout(() => callback(tech), 2000)
    }
    console.log("Calling callBackTech finished!");
}

function logTechDetails(tech) {
    if (tech) {
        console.log("The technology used is: " + tech);
    }
}

callBackTech(logTechDetails, "HTML5");
Enter fullscreen mode Exit fullscreen mode

输出

输出异步回调

在这个异步版本中,请注意的输出logTechDetails()打印在最后的位置。

这是因为此回调的异步执行将其执行延迟了 2 秒,直到当前正在执行的任务完成为止。

回调是old-fashioned编写异步 JavaScript 的方式,因为一旦您必须处理多个异步操作,回调就会相互嵌套,以 结尾callback hell

回调地狱

为了避免这种模式的发生,我们现在就来看看Promises

3-承诺:我保证一定的结果!

Promises用于处理 JavaScript 中的异步操作,它们只是表示异步操作的完成或失败。

因此,Promises 有四种状态:

  • pending:承诺的初始状态
  • fulfilled:操作成功
  • 拒绝:操作失败
  • 已结算:操作已完成或已结算,但不再待处理。

这是在 JavaScript 中创建 Promise 的一般语法。

let promise = new Promise(function(resolve, reject) {
    ... code
});
Enter fullscreen mode Exit fullscreen mode

resolvereject分别是操作成功和操作失败时执行的函数。

为了更好地理解Promises工作原理,让我们举一个例子。

  • 杰克的妈妈:嘿,杰克!你能去商店买点牛奶吗?我需要一些牛奶来完成蛋糕。
  • 杰克:当然可以,妈妈!
  • 杰克的妈妈:你做这些的时候,我会准备好做蛋糕的工具。(异步任务)同时,如果你找到了,请告诉我。(成功回调)
  • 杰克:太好了!可是如果我找不到牛奶怎么办?
  • 杰克的妈妈:那你就吃点巧克力吧。(失败回调)

这个比喻并不是十分准确,但我们还是采用它吧。

假设杰克找到了一些牛奶,承诺将会是这样的。

let milkPromise = new Promise(function (resolve, reject) {

    let milkIsFound = true;

    if (milkIsFound) {
        resolve("Milk is found");
    } else {
        reject("Milk is not found");
    }
});
Enter fullscreen mode Exit fullscreen mode

那么,这个承诺可以像这样使用:

milkPromise.then(result => {
    console.log(result);
}).catch(error => {
    console.log(error);
}).finally(() => {
    console.log("Promised settled.");
});
Enter fullscreen mode Exit fullscreen mode

这里 :

  • then():在成功的情况下进行回调,并在承诺得到解决时执行。
  • catch():接受回调,如果失败则执行承诺,如果承诺被拒绝则执行。
  • finally():接受回调函数,并在前提条件确定后返回。当你需要执行一些清理操作时,它非常有用。

现在让我们使用一个真实的例子,通过创建一个承诺来获取一些数据。

let retrieveData = url => {

    return new Promise( function(resolve, reject) {

        let request = new XMLHttpRequest();
        request.open('GET', url);

        request.onload = function() {
          if (request.status === 200) {
            resolve(request.response);
          } else {
            reject("An error occured!");
          }
        };
        request.send();
    })
};
Enter fullscreen mode Exit fullscreen mode

XMLHttpRequest对象可用于在 JavaScript 中发出 HTTP 请求。

让我们使用https://swapi.dev发出星球大战 APIretrieveData的请求。

const apiURL = "https://swapi.dev/api/people/1";

retrieveData(apiURL)
.then( res => console.log(res))
.catch( err => console.log(err))
.finally(() => console.log("Done."))
Enter fullscreen mode Exit fullscreen mode

输出结果如下。

输出

输出

撰写承诺的规则

  • 您不能在代码中同时调用resolvereject。一旦两个函数之一被调用,Promise 就会停止并返回结果。
  • 如果您不调用这两个函数中的任何一个,承诺就会挂起。
  • 您只能向resolve或传递一个参数reject。如果您要传递更多内容,请将所有内容包装在一个对象中。

4 - async/await:当我准备好时就会执行!

async/await语法是随ES2017引入的,旨在帮助使用承诺编写更好的异步代码。

那么,promise 有什么问题呢?
你可以随意串联then()多个 promise,但这样写起来会Promises有点冗长。
以杰克买牛奶为例,他可以:

  • 打电话给他的妈妈;
  • 然后买更多的牛奶;
  • 然后买巧克力;
  • 等等。
milkPromise.then(result => {
        console.log(result);
    }).then(result => {
        console.log("Calling his Mom")
    }).then(result => {
        console.log("Buying some chocolate")
    }).then(() => {
        ...
    })
    .catch(error => {
        console.log(error);
    }).finally(() => {
        console.log("Promised settled.");
    });
Enter fullscreen mode Exit fullscreen mode

让我们看看如何使用async/awaitJavaScript 编写更好的异步代码。

朋友聚会示例

杰克被朋友邀请参加一个聚会。

  • 朋友:你什么时候准备好?我们会来接你。
  • 杰克:20分钟就好。我保证。

嗯,其实杰克30分钟就能准备好。顺便说一句,他的朋友们没等他就去不了派对,所以他们得等着。

以同步的方式,事情看起来会像这样。

let ready = () => {

    return new Promise(resolve => {

        setTimeout(() => resolve("I am ready."), 3000);
    })
}
Enter fullscreen mode Exit fullscreen mode

setTimeout()方法将函数作为参数(回调)并在指定的毫秒数后调用它。

让我们Promise在常规函数中使用它并查看输出。


function pickJack() {

    const jackStatus = ready();

    console.log(`Jack has been picked: ${jackStatus}`);

    return jackStatus;
}

pickJack(); // => Jack has been picked: [object Promise]
Enter fullscreen mode Exit fullscreen mode

为什么会出现这样的结果?Promise因为函数没有很好地处理pickJack
它被当作jackStatus一个普通的对象。

现在是时候告诉我们的函数如何使用asyncawait关键字来处理这个问题。

首先,将async关键字放在函数前面pickJack()

async function pickJack() {
    ...
}
Enter fullscreen mode Exit fullscreen mode

通过async在函数前使用 关键字,JavaScript 会理解该函数将返回一个Promise
即使我们没有明确返回Promise,JavaScript 也会自动将返回的对象包装在 Promise 中。

下一步,await在函数主体中添加关键字。

    ...
    const jackStatus = await ready();
    ...
Enter fullscreen mode Exit fullscreen mode

await使 JavaScript 等待直到Promise解决并返回结果

该函数最终看起来是这样的。

async function pickJack() {

    const jackStatus = await ready();

    console.log(`Jack has been picked: ${jackStatus}`);

    return jackStatus;
}

pickJack(); // => "Jack has been picked: I am ready."
Enter fullscreen mode Exit fullscreen mode

这就是全部内容了async/await

此语法具有简单的规则:

  • 如果您创建的函数处理异步任务,请使用async关键字标记该函数。

  • await关键字暂停函数执行,直到承诺得到解决(履行或拒绝)。

  • 异步函数总是返回一个Promise

async/await下面是使用和方法的实际示例fetch()fetch()允许您发出类似的网络请求,XMLHttpRequest但这里最大的区别是 Fetch API 使用 Promises。

这将帮助我们使从https://swapi.dev获取的数据更加清晰和简单。

async function retrieveData(url) {
    const response = await fetch(url);

    if (!response.ok) {
        throw new Error('Error while fetching resources.');
    }

    const data = await response.json()

    return data;
};
Enter fullscreen mode Exit fullscreen mode

const response = await fetch(url);将暂停函数执行,直到请求完成。
那么为什么呢await response.json()?你可能会问自己。

初始fetch()调用后,仅读取了标头。主体数据需要先从传入流中读取,然后再解析为 JSON。

由于从 TCP 流读取(发出请求)是异步的,因此.json()操作最终也是异步的。

然后我们在浏览器中执行代码。

retrieveData(apiURL)
.then( res => console.log(res))
.catch( err => console.log(err))
.finally(() => console.log("Done."));
Enter fullscreen mode Exit fullscreen mode

这就是全部async/await

结论

在本文中,我们学习了回调,async/await以及如何Promise在 JavaScript 中编写异步代码。如果你想了解更多关于这些概念的知识,请查看这些精彩的资源。

文章来源:https://dev.to/koladev/a-simple-guide-to-asynchronous-javascript-callbacks-promises-async-await-4m03
PREV
在前端保护 API 密钥的最快方法(几分钟内)
NEXT
🎊 无需 SVG!使用 Tailwind CSS 创建内容加载动画