JavaScript Promise 的真正工作原理

Promise 是处理异步代码的一种技术,
也称为脱离回调地狱的头等舱门票。


3 承诺状态

  • 待定状态
  • 已解决状态
  • 拒绝状态

理解 JavaScript Promise

什么是承诺?

通常,承诺被定义为最终可用的值的代理。

Promise 多年来一直是 JavaScript 的一部分(在 ES2015 中标准化并引入)。最近,asyncawait关键字(在 ES2017 中引入)更深入地集成和清理了 JavaScript 中 Promise 的语法。

异步函数在幕后使用 Promise,因此 - 特别是随着当今的分布式云架构变得越来越普遍 - 了解 Promise 是什么以及它们如何工作比以往任何时候都更加重要!

现在我们知道承诺很重要,让我们深入探讨一下。


Promise 如何发挥作用(简要说明)

你的代码调用了一个承诺。该承诺将以所谓的待处理状态开始。这是什么意思?

这意味着调用函数将在 Promise 挂起时继续执行。一旦承诺得到解决, 调用函数将获取承诺所请求的数据。

Promise 开始于待处理状态,最终以****已解决状态拒绝状态结束。

无论最终结果是处于已解决状态的 承诺还是处于拒绝状态的承诺,都将调用回调。

我们定义两个单独的回调。

当 Promise 以已解决状态结束时,一个回调会处理从 Promise 返回的数据。

当 Promise 以拒绝状态结束时,另一个回调处理 Promise 返回的数据。

我们通过将回调函数传递给then 来定义处理以已****解决状态结束的 Promise 数据的回调函数。****

我们通过将回调函数传递给catch来定义处理以****拒绝状态结束的 Promise 数据的回调函数。****

使用 axios npm 库的示例

ini 复制代码
axios.get(endpoint)
     .then(data => resolvedPromiseCallbackFunction(data))
     .catch(errors => rejectedPromiseCallbackFunction(errors))

哪些 JavaScript API 使用 Promise?

您自己的代码和库很可能自始至终都使用 Promise。值得注意的是,Promise 实际上是由标准现代 Web APIS 使用的。这里有几个也使用 Promise 的 Web API。

在现代 JavaScript 中,您不太可能发现自己处于不使用 Promise 的情况 - 所以让我们深入研究并开始理解它们。


创造承诺

JavaScript 有一个 Promise API。Promise API 公开了一个 Promise 构造函数,您可以使用以下方法对其进行初始化new Promise()

javascript 复制代码
let complete = trueconst hasItCompleted = new Promise((resolve, reject) => {
   if (complete) { 
      const completed = 'Here is the thing I built'
      resolve(completed)
   } else {
     const withReason = 'Still doing something else'
     reject(withReason)
   }})

如图所示,我们检查complete全局常量。如果complete为 true,则 Promise 切换到已解决 状态(也称为解析回调,它将 Promise 切换到已解决 状态)。否则,如果complete为 false,则reject执行回调,将 Promise 置于拒绝状态。

好吧 - 很简单,如果我们调用回调resolve,那么我们的 Promise 就会切换到已解决 状态,就像我们使用reject回调一样,我们的 Promise 会切换到拒绝状态。但这给我们留下了一个问题。

如果我们既不调用resolve也不调用reject回调怎么办?好吧,正如您可能正在组合的那样,承诺仍处于待处理状态。

很简单,三个状态 - 两个回调函数切换到Resolved StateRejected State ,如果我们都不调用回调,那么我们只是保持在Pending State


有前途

您可能遇到的一个更常见的例子是一种称为Promisifying 的技术。

Promisifying是一种能够使用接受回调的经典 JavaScript 函数并让它返回一个 Promise 的方法:

javascript 复制代码
const fileSystem = require('fs')const getFile = file => {
    return new Promise((resolve, reject) => {
        fileSystem.readFile(file, (err, data) => {
           if (err) { 
               reject(err)
               return 
           }  
           resolve(data)
        })
    })}let file = '/etc/passwd'getFile(file)
  .then(data => console.log(data))
  .catch(err => console.error(err))

在 Node.js 的最新版本中,您无需
对许多 API 进行手动转换。util 模块中有一个
promisifying 函数可以 为您执行此操作,前提是您所 promisifying 的函数
具有正确的签名。


消费承诺

new Promise()现在了解了如何使用Promisifying 技术创建 Promise ,让我们来谈谈使用Promise。

我们如何使用承诺(又名我们如何使用承诺)

javascript 复制代码
const isItDoneYet = new Promise(/* ... as above ... */)//...const checkIfItsDone = () => {
  isItDoneYet
    .then(ok => {
      console.log(ok)
    })
    .catch(err => {
      console.error(err)
    })}

运行将指定当承诺解决(在调用中)或拒绝(在调用中)checkIfItsDone()时要执行的函数。isItDoneYet``then``catch


流畅地链接 Promise

如果我们想在前一个 Promise 返回后直接调用另一个 Promise 该怎么办?我们可以做到这一点,这简单地称为创建承诺链。

链式 Promise 的示例可以在 Fetch API 中找到,它可用于获取资源并在获取资源时对要执行的 Promise 链进行排队(先进先出行)。

首先,我们首先指出Fetch API 是一种基于承诺的机制。调用该fetch()方法相当于使用 定义我们自己的 Promise new Promise()

下面是一个将 Promise 流畅地链接在一起的示例:

scss 复制代码
const status = response => 
     response.status >= 200 && response.status < 300
          ? Promise.resolve(response)
          : Promise.reject(new Error(response.statusText))   const json = response => response.json()fetch('/items.json').then(status).then(json).then(data => console.log('Request success (with json): ', data)).catch(error => console.log('Request failed: ', error) 

" node-fetch是 Node.js 运行时上 window.fetch 兼容 API 的最少代码。"

那么,我们刚刚做了什么?

好吧,在上面的示例中,我们调用从域根中找到的文件fetch()中获取项目列表。items.json

然后我们创建一个承诺链。

运行fetch()会返回响应

  • 响应包含status(数字 HTTP 状态代码)
  • 响应包含statusText(字符串消息,如果OK一切成功)

response还包含一个可调用的方法json()。Responses json 方法返回一个承诺,该承诺将通过处理并转换为JSON.

然后我们的链中有一个最终的 Promise 作为匿名回调函数传入。

ini 复制代码
data => console.log('Request success (with json): ', data)

这个函数只是记录我们成功了,并且控制台记录了成功的请求 json 数据。

_"如果第一个承诺被拒绝了怎么办?"

如果第一个 Promise 被拒绝,或者第二个 Promise,或者第三个 Promise - 那么,无论步骤如何,我们都会自动默认为catch直观地显示在我们流畅的 Promise 链末尾的回调方法。


处理错误

我们有一个承诺链,有些东西失败了,呃哦 - 那么会发生什么?

如果 Promise 链中的任何内容失败并引发错误或最终将 Promise 的状态设置为Rejected Promise State catch() ,则控制将直接转到Promise 链中最近的语句。

javascript 复制代码
new Promise((resolve, reject) => {
  throw new Error('Error')}).catch(err => {
  console.error(err)})// ornew Promise((resolve, reject) => {
  reject('Error')}).catch(err => {
  console.error(err)})

级联错误

如果我们在 a中引发错误怎么办catch()?好吧,检查一下 - 我们可以简单地附加第二个catch(). 第二个catch()将处理错误(或更具体地说是错误消息)等等。

javascript 复制代码
new Promise((resolve, reject) => {
  throw new Error('Error')})
  .catch(err => {
    throw new Error('Error')
  })
  .catch(err => {
    console.error(err)
  })

承诺编排

好的,现在我们对于单个 Promise 以及我们对 Promise 的基本理解已经很扎实了。

更进一步,让我们问另一个问题。如果您需要同步不同的 Promise - 比如说从多个端点提取数据并处理所有创建并用于从这些不同端点检索结果的 Promise 中已解析的 Promise 数据 - 我们会怎么做?

我们如何同步不同的 Promise 并在它们全部解决后执行某些操作?

回答: Promise.all()

Promise.all()帮助我们定义一个承诺列表,并在它们全部解决后执行某些操作 - 它允许我们同步承诺。

Promise.all() 例子:

ini 复制代码
const one = fetch('/one.json')const two = fetch('/two.json')Promise.all([one, two])
  .then(response => console.log('Array of results: ', response)
  .catch(errors => console.error(errors))

通过解构,我们可以将这个例子简化为:

javascript 复制代码
const [one, two] = [fetch('/one.json'), fetch('/two.json')]Promise.all([one, two]).then(([resA, resB]) => console.log('results: ', resA, resB))

Promise.race()

如果我们想要从这些多个 API 获取所有数据,但实际上只需要从一个端点返回足够的数据来显示在我们的页面上,该怎么办?

也就是说,无论如何,我们都需要解决所有的 Promise,但是我们希望对第一个已解决的 Promise 中的数据执行某些操作,并且我们不关心哪个 Promise 首先得到解决。

为了处理第一个已解决的 Promise 中的数据,我们可以使用Promise.race().

Promise.race()当您传递给它的第一个 Promise 解析时运行,并且它仅运行一次附加的回调,并得到第一个 Promise 解析的结果。

例子

javascript 复制代码
const first = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, 'first')})const second = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'second')})Promise.race([first, second]).then(result => {
  console.log(result) // second})

s.juejin.cn/ds/ieyRoEGe...

相关推荐
M_emory_11 分钟前
解决 git clone 出现:Failed to connect to 127.0.0.1 port 1080: Connection refused 错误
前端·vue.js·git
Ciito15 分钟前
vue项目使用eslint+prettier管理项目格式化
前端·javascript·vue.js
成都被卷死的程序员1 小时前
响应式网页设计--html
前端·html
fighting ~1 小时前
react17安装html-react-parser运行报错记录
javascript·react.js·html
老码沉思录1 小时前
React Native 全栈开发实战班 - 列表与滚动视图
javascript·react native·react.js
abments1 小时前
JavaScript逆向爬虫教程-------基础篇之常用的编码与加密介绍(python和js实现)
javascript·爬虫·python
mon_star°1 小时前
将答题成绩排行榜数据通过前端生成excel的方式实现导出下载功能
前端·excel
Zrf21913184551 小时前
前端笔试中oj算法题的解法模版
前端·readline·oj算法
老码沉思录1 小时前
React Native 全栈开发实战班 - 状态管理入门(Context API)
javascript·react native·react.js
文军的烹饪实验室2 小时前
ValueError: Circular reference detected
开发语言·前端·javascript