JavaScript Promises 和异步编程简介 Promises 方法 Async 和 Await 结论

2025-06-04

JavaScript Promises 和异步编程简介

承诺

方法

Async 和 Await

结论

立即在http://jauyeung.net/subscribe/订阅我的电子邮件列表

在 Twitter 上关注我:https://twitter.com/AuMayeung

更多文章请访问https://medium.com/@hohanga

由于 JavaScript 是单线程语言,同步代码一次只能执行一行。这意味着,如果同步代码的运行时间超过瞬时,它将停止其余代码的运行,直到正在运行的代码完成为止。为了防止运行时间不确定的代码阻碍其他代码的运行,我们需要使用异步代码。


承诺

要在 JavaScript 中实现这一点,我们可以在代码中使用 Promise。Promise 是表示运行时间不确定的进程的对象,它可能导致成功或失败。要在 JavaScript 中创建 Promise,我们使用一个Promise用于创建 Promise 的构造函数对象。Promise构造函数接受一个名为 executor 的函数,该函数具有resolvereject参数。这两个参数都是函数,我们调用它们来履行 Promise(这意味着以某个值成功结束它)或拒绝 Promise(这意味着返回某个错误值并将 Promise 标记为失败)。函数的返回值将被忽略。因此,Promise 不能返回除 Promise 之外的任何值。

例如,我们可以在 JavaScript 中定义一个承诺,如以下代码所示:

const promise = new Promise((resolve, reject) => {  
  setTimeout(() => resolve('abc'), 1000);  
});

上面的代码创建了一个 Promise,它会abc在一秒后使用值来实现该 Promise。因为我们setTimeout在执行器函数内部运行,以便abc在一秒内使用值来解决该 Promise,所以这是异步代码。我们无法abc在的回调函数中返回值setTimeout,所以我们必须调用resolve('abc')来获取已解析的值。我们可以使用then函数访问已实现 Promise 的解析值。该then函数接受一个回调函数,该回调函数将已实现 Promise 的解析值作为参数。您可以在那里获取该值并对其执行所需的操作。例如,我们可以执行以下操作:

const promise = new Promise((resolve, reject) => {  
  setTimeout(() => resolve('abc'), 1000);  
});

promise.then((val) => {  
  console.log(val);  
})

当我们运行上面的代码时,我们应该会看到abc日志。正如我们所见,当 Promise 通过调用函数实现时,Promise 将会提供相应的值resolve

一个 Promise 有三种状态。它可以是 pending,表示该 Promise 已被 fulfilled 或 rejected。它可以是 fulfilled,表示操作已成功完成。也可以是 rejection,表示该 Promise 操作失败。

待处理的 Promise 可以以某个值完成,也可以以某个错误被拒绝。当 Promise 完成时,函数会获取相应的解析值then,并调用传入函数的回调函数then。如果 Promise 被拒绝,我们可以选择使用函数捕获错误catch,该函数也接受一个带有错误信息的回调函数。thencatch函数都返回 Promise,因此它们可以链接在一起。

例如,我们可以写如下内容:

const promise = (num) => {  
  return new Promise((resolve, reject) => {  
    setTimeout(() => {  
      if (num === 1) {  
        resolve('resolved')  
      } else {  
        reject('rejected')  
      }  
    }, 1000);  
  });  
}

promise(1)  
  .then((val) => {  
    console.log(val);  
  })  
  .catch((error) => {  
    console.log(error);  
  })promise(2)  
  .then((val) => {  
    console.log(val);  
  })  
  .catch((error) => {  
    console.log(error);  
  })

在上面的代码中,我们有一个函数,它返回一个 JavaScript Promise,当值为 1 时,promise它会执行 Promise 的值,否则会抛出错误并拒绝 Promise 。因此我们运行:resolvednumrejected

promise(1)  
  .then((val) => {  
    console.log(val);  
  })  
  .catch((error) => {  
    console.log(error);  
  })

然后then函数运行,由于值为 1,函数调用返回的 Promisepromise(1)被满足,num并且解析后的值可以在 中设置val。因此,运行时console.log(val)resolved记录日志。运行以下代码:

promise(2)  
  .then((val) => {  
    console.log(val);  
  })  
  .catch((error) => {  
    console.log(error);  
  })

然后catch函数运行,因为函数调用返回的 Promisepromise(2)被拒绝了,而这个被拒绝的错误值可以在 error 中设置。所以运行时console.log(error)rejected打印日志。

JavaScript 的 Promise 对象具有以下属性:lengthprototypelength的值为 1,表示构造函数参数的数量,即始终为 1。prototype属性表示 Promise 对象的原型。

Promise 还具有一个finally方法,用于运行无论 Promise 是 fulfilled 还是 rejected 都会运行的代码。该finally方法接受一个回调函数作为参数,无论 Promise 的结果如何,任何你想要运行的代码都会在这个回调函数中运行。例如,如果我们运行:

Promise.reject('error')  
  .then((value) => {  
    console.log(value);  
  })  
  .catch((error) => {  
    console.log(error);  
  })  
  .finally(() => {  
    console.log('finally runs');  
  })

然后我们获取errorfinally runs记录,因为原始承诺因原因而被拒绝error。然后该方法回调中的任何代码finally都会运行。

使用 Promise 编写异步代码的主要好处是我们可以使用它们按顺序运行异步代码。为此,我们可以将 Promise 与then函数链接起来。该then函数接受一个在 Promise 实现时运行的回调函数。当 Promise 被拒绝时,它还接受第二个参数。要链接 Promise,我们必须返回另一个 Promise 作为函数第一个回调函数的返回值then。如果我们不想将另一个 Promise 链接到现有的 Promise,它可以返回其他值,比如什么也不返回。我们可以返回一个值,该值将被解析并且可以在下一个then函数中检索到。它也可以抛出一个错误。然后,返回的 Promisethen将被拒绝,并将抛出的错误作为值。它还可以返回一个已经实现或被拒绝的 Promise,当一个函数链接在它之后时,它将获得实现的值then,或者在函数的回调中获取错误原因catch

例如,我们可以写以下内容:

Promise.resolve(1)  
  .then(val => {  
    console.log(val);  
    return Promise.resolve(2)  
  })  
  .then(val => {  
    console.log(val);  
  })Promise.resolve(1)  
  .then(val => {  
    console.log(val);  
    return Promise.reject('error')  
  })  
  .then(val => {  
    console.log(val);  
  })  
  .catch(error => console.log(error));Promise.resolve(1)  
  .then(val => {  
    console.log(val);  
    throw new Error('error');  
  })  
  .then(val => {  
    console.log(val);  
  })  
  .catch(error => console.log(error));

在第一个例子中,我们将 Promise 串联起来,它们都解析为一个值。所有 Promise 都已解析为值。在第二个和最后一个例子中,我们拒绝第二个 Promise 或抛出一个错误。它们的作用相同。第二个 Promise 被拒绝,错误原因将记录在catch函数的回调中。我们也可以串联待处理的 Promise,如下例所示:

const promise1 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(1), 1000);  
});

const promise2 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(2), 1000);  
});

promise1  
  .then(val => {  
    console.log(val);  
    return promise2;  
  })  
  .then(val => {  
    console.log(val);  
  })  
  .catch(error => console.log(error));

第一个函数的回调then返回promise2,这是一个待处理的承诺。


方法

JavaScript 承诺具有以下方法。

Promise.all(可迭代)

Promise.all接受一个可迭代对象,该对象允许我们在某些计算机上并行运行多个 Promise,而在其他计算机上串行运行。这对于运行多个互不依赖的 Promise 非常方便。它接受一个包含 Promise 列表(通常为数组)的可迭代对象,然后返回一个 Promise,Promise当迭代对象中的所有 Promise 都解析后,该 Promise 也解析成功。

例如,我们可以编写如下代码来运行多个承诺Promise.all

const promise1 = Promise.resolve(1);  
const promise2 = 2;  
const promise3 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(3), 1000);  
});  
Promise.all([promise1, promise2, promise3])  
  .then((values) => {  
    console.log(values);  
  });

如果我们运行上面的代码,应该console.log会打印日志[1,2,3]。如我们所见,它仅在所有 Promise 都已完成后才返回已解决的值。如果其中一些 Promise 被拒绝,那么我们将不会获得任何已解决的值。相反,我们将获得被拒绝的 Promise 返回的任何错误值。它会在第一个被拒绝的 Promise 处停止,并将该值发送给catch函数的回调函数。例如,如果我们有:

const promise1 = Promise.resolve(1);  
const promise2 = Promise.reject(2);  
const promise3 = new Promise((resolve, reject) => {  
  setTimeout(() => reject(3), 1000);  
});  
Promise.all([promise1, promise2, promise3])  
  .then((values) => {  
    console.log(values);  
  })  
  .catch(error => {  
    console.log(error);  
  });

console.log然后我们从函数的回调中得到记录的 2 catch

Promise.allSettled

Promise.allSettled返回一个 Promise,该 Promise 在所有给定的 Promise 都解析或拒绝后解析。它接受一个可迭代对象,其中包含一组 Promise,例如一个 Promise 数组。返回的 Promise 的解析值是一个包含每个 Promise 最终状态的数组。例如,假设我们有:

const promise1 = Promise.resolve(1);  
const promise2 = Promise.reject(2);  
const promise3 = new Promise((resolve, reject) => {  
  setTimeout(() => reject(3), 1000);  
});  
Promise.allSettled([promise1, promise2, promise3])  
  .then((values) => {  
    console.log(values);  
  })

如果我们运行上面的代码,我们会得到一个包含三个条目的数组,每个条目都是一个对象,它包含已完成的 Promise 的status和属性,以及一个包含已拒绝的 Promise 的属性。例如,上面的代码将记录成功 Promise 的状态会被记录,拒绝 Promise 的状态也会被记录。valuestatusreason{status: “fulfilled”, value: 1}{status: “rejected”, reason: 2}{status: “rejected”, reason: 3}fulfilledrejected

Promise.race

Promise.race方法返回一个 Promise,该 Promise 解析为第一个已完成的 Promise 的已解析值。它接受一个可迭代对象,其中包含一组 Promise,例如一个 Promise 数组。如果传入的可迭代对象为空,则返回的 Promise 将永远处于待处理状态。如果可迭代对象包含一个或多个非 Promise 值,或者包含已完成的 Promise,则将Promise.race返回其中第一个。例如,如果我们有:

const promise1 = Promise.resolve(1);  
const promise2 = Promise.resolve(2);  
const promise3 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(3), 1000);  
});  
Promise.race([promise1, promise2, promise3])  
  .then((values) => {  
    console.log(values);  
  })

然后我们看到它1被打印出来了。这是因为promise1它是第一个被解析的,因为它在下一行运行之前就被立即解析了。同样,如果我们在数组中有一个非 Promise 值作为参数传入,就像下面的代码一样:

const promise1 = 1;  
const promise2 = Promise.resolve(2);  
const promise3 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(3), 1000);  
});  
Promise.race([promise1, promise2, promise3])  
  .then((values) => {  
    console.log(values);  
  })

然后,我们得到相同的输出,因为它是我们传递给方法的数组中的非 Promise 值Promise.race。同步代码始终在异步代码之前运行,因此同步代码的位置无关紧要。如果我们有:

const promise1 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(1), 2000);  
});

const promise2 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(2), 1000);  
});

const promise3 = 3;  
Promise.race([promise1, promise2, promise3])  
  .then((values) => {  
    console.log(values);  
  })

然后我们3进行记录,因为setTimeout将回调函数放入队列中以便稍后运行,所以它将比同步代码更晚运行。

最后,如果我们有:

const promise1 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(1), 2000);  
}); 

const promise2 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(2), 1000);  
});

Promise.race([promise1, promise2])  
  .then((values) => {  
    console.log(values);  
  })

然后我们2进入控制台日志,因为一秒钟内解决的承诺将比两秒钟内解决的承诺更早得到解决。

Promise.reject

Promise.reject返回一个因原因而被拒绝的 Promise。使用 的实例对象来拒绝 Promise 很有用Error。例如,如果我们有以下代码:

Promise.reject(new Error('rejected'))  
  .then((value) => {  
    console.log(value);  
  })  
  .catch((error) => {  
    console.log(error);  
  })

然后我们就rejected登录了。

Promise.resolve

Promise.resolve返回一个 Promise,该 Promise 解析为传递给函数参数的值resolve。我们还可以传入一个具有then属性的对象,其值是 Promise 的回调函数。如果该值包含then方法,则该 Promise 将使用该函数实现的值来实现then。也就是说,函数的第一个参数与函数的值then相同resolve,第二个参数与函数的值相同。reject.例如,我们可以编写以下内容:

Promise.resolve(1)  
  .then((value) => {  
    console.log(value);  
  })

然后我们得到1记录,因为1这是我们传递给函数的值resolve,以返回具有已解析值的承诺1

如果我们传入一个带有then方法的对象,如下面的代码所示:

Promise.resolve({  
    then(resolve, reject) {  
      resolve(1);  
    }  
  })  
  .then((value) => {  
    console.log(value);  
  })

然后我们得到1记录的值。这是因为该Promise.resolve函数将运行该then函数,并且resolve设置为then属性的函数参数将被假定为resolve在 Promise 中调用该函数的函数。如果我们在传入的对象中调用reject该函数内部的函数then,那么我们会得到一个被拒绝的 Promise,如下例所示:

Promise.resolve({  
    then(resolve, reject) {  
      reject('error');  
    }  
  })  
  .then((value) => {  
    console.log(value);  
  })  
  .catch((error) => {  
    console.log(error);  
  })

在上面的代码中,error由于承诺被拒绝,我们进行了记录。


Async 和 Await

使用asyncawait,我们可以缩短 Promise 代码。在使用async和之前await,我们必须使用then函数,并且必须将回调函数作为所有then函数的参数。由于 Promise 很多,这会使代码变得很长。相反,我们可以使用asyncawait语法来替换then函数及其关联的回调。例如,我们可以将以下代码从:

const promise1 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(1), 2000);  
});

const promise2 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(2), 1000);  
});

promise1  
  .then((val1) => {  
    console.log(val1);  
    return promise2;  
  })  
  .then((val2) => {  
    console.log(val2);  
  })

到:

const promise1 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(1), 2000);  
});  

const promise2 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(2), 1000);  
});

(async () => {  
  const val1 = await promise1;  
  console.log(val1)  
  const val2 = await promise2;  
  console.log(val2)  
})()

我们将then和 回调替换为await。然后,我们可以将每个 Promise 的解析值赋值为变量。请注意,如果我们使用 来await作为 Promise 代码,则必须async像上例一样添加函数签名。为了捕获错误,catch我们使用 子句,而不是将函数链接在末尾catch。此外,我们不是finally在底部链接函数以在 Promise 结束时运行代码,而是在 子句finally后使用 子句catch

例如,我们可以写:

const promise1 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(1), 2000);  
});  
const promise2 = new Promise((resolve, reject) => {  
  setTimeout(() => reject('error'), 1000);  
});

(async () => {  
  try {  
    const val1 = await promise1;  
    console.log(val1)  
    const val2 = await promise2;  
    console.log(val2)  
  } catch (error) {  
    console.log(error)  
  } finally {  
    console.log('finally runs');  
  }})()

在上面的代码中,我们将 Promise 的解析值赋给了一个变量,而不是像上一行then那样在函数的回调中获取值const response = await promise1。此外,我们使用了try...catch...finally代码块来捕获被拒绝的 Promise 的错误,并使用了finally子句而不是finally传入回调的函数来创建无论 Promise 发生什么都会运行的代码。

与其他使用 Promise 的函数一样,async函数始终返回 Promise,不能返回任何其他内容。在上面的示例中,我们展示了如何以比将then回调作为参数传入的函数更简洁的方式链接 Promise。


结论

有了 Promise,我们可以轻松编写异步代码。Promise 是表示一个运行时间不确定的进程的对象,该进程可能成功也可能失败。要在 JavaScript 中创建 Promise,我们使用一个Promise构造函数对象。

构造Promise函数接受一个名为 executor 的函数,该函数包含resolvereject参数。这两个参数都是函数,我们调用它们来要么实现 Promise(这意味着成功结束并返回某个值),要么拒绝 Promise(这意味着返回某个错误值并将 Promise 标记为失败)。该函数的返回值会被忽略。因此,Promise 不能返回除 Promise 之外的任何值。

Promise 是可链式执行的,因为 Promise 会返回 Promise。Promisethen的函数可以抛出错误、返回已解决的值,或者返回其他处于待处理、已完成或已拒绝状态的 Promise。

文章来源:https://dev.to/aumayeung/introduction-to-javascript-promises-and-async-programming-255d
PREV
通过示例学习 Go:第 2 部分 - 使用 Go 创建 HTTP REST API 服务器
NEXT
如何使用 React 制作日历应用