Promise 是处理异步代码的一种技术,
也称为脱离回调地狱的头等舱门票。
3 承诺状态
- 待定状态
- 已解决状态
- 拒绝状态
理解 JavaScript Promise
什么是承诺?
通常,承诺被定义为最终可用的值的代理。
Promise 多年来一直是 JavaScript 的一部分(在 ES2015 中标准化并引入)。最近,async
和await
关键字(在 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 State 或Rejected 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})