5 分钟内完成异步 JavaScript

2025-06-08

5 分钟内完成异步 JavaScript

 

JavaScript 利用回调、promise、async 和 await 特性来支持异步编程。我们不会深入探讨每个主题的太多细节,但本文应该能为您提供一个入门的简单介绍。让我们开始吧!

 

示例设置

看下面这个简单的例子。我们有一个预先填充了数字的初始数组,一个 getNumbers 函数循环遍历数组并输出数组中的每个元素,还有一个 addNumber 函数接收一个数字并将其添加到数组中。

const numbers = [1, 2];

function getNumbers() {
  numbers.forEach(number => console.log(number))
}

function addNumber(number) {
  numbers.push(number);
}

getNumbers(numbers) // 1, 2
addNumber(3);
getNumbers(numbers) // 1, 2, 3

 

问题

现在,假设两个函数调用都需要一些时间来执行,因为我们正在向后端服务器发出请求。让我们使用内置的 setTimeout 方法模拟这种情况,并将我们的逻辑包装在其中。

const numbers = [1, 2];

function getNumbers() {
  setTimeout(() => {
    numbers.forEach(number => console.log(number))
  }, 1000)
}

function addNumber(number) {
  setTimeout(() => {
  numbers.push(number)
  }, 2000)
}

getNumbers(numbers) // 1, 2
addNumber(3)
getNumbers(numbers) // 1, 2 ... Why?

现在看看控制台。它的行为与之前不同。这是因为 'addNumber' 函数需要 2 秒才能运行,而 'getNumbers' 函数需要 1 秒才能运行。因此,'addNumber' 函数会在两个 'getNumbers' 函数调用完成后才会执行。'addNumber(3)' 函数调用不会等待上一行执行完毕。

 

回调

在这种情况下,逐行调用异步函数行不通。还有其他方法可以确保一个函数只在另一个函数执行完成后才执行吗?回调函数可以帮到我们!在 JavaScript 中,函数可以作为参数传递。因此,我们可以将 getNumbers 函数传递给 addNumber 函数,并在添加数字后执行该函数。

const numbers = [1, 2];

function getNumbers() {
  setTimeout(() => {
    numbers.forEach(number => console.log(number))
  }, 1000)
}

function addNumber(number, callback) {
  setTimeout(() => {
  numbers.push(number)
  callback();
  }, 2000)
}

getNumbers(numbers) // 1, 2
addNumber(3, getNumbers) // 1, 2, 3

以下是我们代码库的流程。“getNumbers”在 1 秒后调用。“addNumbers”在 2 秒后调用(比“getNumbers”晚 1 秒)。将数字推送到数组后,它会再次调用“getNumbers”,这又耗时 1 秒。程序在 3 秒后完全终止。为了了解更多关于回调函数的知识,我之前写了一篇深入的文章。

 

承诺

以下是相同代码的重写版本。我们将不再使用回调函数并直接调用它,因此让我们修改“addNumber”函数,使其不再接受第二个参数。相反,它将new Promise()立即使用关键字返回一个 Promise。Promise 可以使用 resolve 和 reject 函数,这些函数可以通过传入的参数进行解析和拒绝,并可以在特定操作后调用。如果一切顺利,您可以调用 resolve() 函数。

 

const numbers = [1, 2];

function getNumbers() {
  setTimeout(() => {
    numbers.forEach(number => console.log(number))
  }, 1000)
}

function addNumber(number) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      numbers.push(number);
      resolve();
    }, 2000)
  });
}

addNumber(3).then(getNumbers) // 1, 2, 3 after 3 seconds

当 Promise 真正返回时,我们可以使用then关键字将其串联起来。然后,您可以传入一个函数定义,在 Promise 解析后调用该函数!太棒了!但是,如果出现网络超时等错误怎么办?我们可以使用 Reject 关键字来指示操作失败。让我们手动拒绝它。

const numbers = [1, 2];

function getNumbers() {
  setTimeout(() => {
    numbers.forEach(number => console.log(number))
  }, 1000)
}

function addNumber(number) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      numbers.push(number);
      const isAdded = false;
      if (isAdded) {
        resolve();
      } else {
        reject("There was an error")
      }
    }, 2000)
  });
}

addNumber(3).then(getNumbers).catch((e) => console.log(e)) // There was an error

注意,我们可以传入一个字符串,该字符串可以通过 using 捕获.catch,并通过其第一个参数获取。我们也可以通过传入一些数据并在then()方法内部接收,对 resolve 方法执行相同的操作。

 

异步与等待

让我们使用相同的代码,并使用 async 和 await!这里有个剧透!我们仍然需要返回一个 Promise,但处理方式有所不同。来看看吧。

const numbers = [1, 2];

function getNumbers() {
  setTimeout(() => {
    numbers.forEach(number => console.log(number))
  }, 1000)
}

function addNumber(number) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      numbers.push(number);
      const isAdded = true;
      if (isAdded) {
        resolve();
      } else {
        reject("There was an error")
      }
    }, 2000)
  });
}

async function initialize() {
  await addNumber(3);
  getNumbers();
}

initialize(); // 1, 2, 3

我们创建了一个名为“initialize”的函数,而不是将 then 和 catch 链接到 addNumber 调用。使用“await”关键字需要在其包装函数前面添加“async”关键字。此外,“await”关键字使我们的代码更直观易懂,因为即使代码是异步的,现在也可以逐行读取!

现在,错误处理怎么样?

const numbers = [1, 2];

function getNumbers() {
  setTimeout(() => {
    numbers.forEach(number => console.log(number))
  }, 1000)
}

function addNumber(number) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      numbers.push(number);
      const isAdded = false;
      if (isAdded) {
        resolve();
      } else {
        reject("There was an error")
      }
    }, 2000)
  });
}

async function initialize() {
  try {
    await addNumber(3);
    getNumbers();
  } catch (e) {
    console.log(e);
  }
}

initialize(); // There was an error

让我们在初始化函数中使用 try 和 catch 。如果 Promise 被拒绝,我们的 catch 代码块就会运行。

 

概括

我们学习了几种处理异步 JavaScript 的不同方法。我个人更喜欢使用 async 和 await ,因为它们易于编写和思考。但其他方法也有其适用之处,尤其是回调,因为有些 API 只支持回调。感谢您的阅读,让我们用新学到的知识编写一些严肃的代码吧!

此示例代码受到 Brad Traversy 的 youtube视频的启发。

 

鏂囩珷鏉ユ簮锛�https://dev.to/atlassian/asynchronous-javascript-in-under-5-minutes-5dbg
PREV
JavaScript 箭头函数简介
NEXT
数组速查表