如果你不熟悉JavaScript,并且很难理解Promise的工作原理,希望本文能帮助您更清楚地了解Promise。话虽如此,本文针对的是那些对Promise不太熟悉的人。这篇文章不会讨论使用async / await执行Promise(尽管它们在功能上是一样的,但在大多数情况下 async/await 才是真正的语法糖)。
实际上,在JavaScript原生之前,承诺就已经存在了一段时间。例如,在promises成为原生之前实现该模式的两个库是Q和when。
那么什么是Promise?Promise是JS对象,它们用于表示一个异步操作的最终完成 (或失败), 及其结果值.查看MDN 您可以通过使用回调方法或使用Promise执行异步操作来获得结果。但是两者之间有一些细微的差异。
两者之间的主要区别在于,使用回调方法时,我们通常只是将回调传递给一个函数,该函数将在完成时被调用以获取某些结果。但是,在Promise中,您将回调附加在返回的Promise对象上。CallBacks:
function getMoneyBack(money, callback) {
if (typeof money !== 'number') {
callback(null, new Error('money is not a number'))
} else {
callback(money)
}
}
const money = getMoneyBack(1200)
console.log(money)
Promises:
function getMoneyBack(money) {
return new Promise((resolve, reject) => {
if (typeof money !== 'number') {
reject(new Error('money is not a number'))
} else {
resolve(money)
}
})
}
getMoneyBack(1200).then((money) => {
console.log(money)
})
它们是JS中构成Promise的核心部分。
所以,我们为什么需要JS中的Promise?
为了明白这个问题,我们得先来聊聊为什么在大多数的JS开发者中,仅仅使用CallBack的方法是远远不够的。
使用回调方法的一个常见问题是,当我们最终不得不一次执行多个异步操作时,我们很容易以所谓的回调地狱告终,这可能会成为噩梦,因为它导致难以管理且难读取。换句话说,这是每个开发者的噩梦。
下面是一个简单的例子:
function getFrogsWithVitalSigns(params, callback) {
let frogIds, frogsListWithVitalSignsData
api.fetchFrogs(params, (frogs, error) => {
if (error) {
console.error(error)
return
} else {
frogIds = frogs.map(({ id }) => id)
// The list of frogs did not include their health information, so lets fetch that now
api.fetchFrogsVitalSigns(
frogIds,
(frogsListWithEncryptedVitalSigns, err) => {
if (err) {
// do something with error logic
} else {
// The list of frogs health info is encrypted. Our friend texted us the secret key to use in this step. This is used to decrypt the list of frogs encrypted health information
api.decryptFrogsListVitalSigns(
frogsListWithEncryptedVitalSigns,
'pepsi',
(data, errorr) => {
if (errorrr) {
throw new Error('An error occurred in the final api call')
} else {
if (Array.isArray(data)) {
frogsListWithVitalSignsData = data
} else {
frogsListWithVitalSignsData = data.map(
({ vital_signs }) => vital_signs,
)
console.log(frogsListWithVitalSignsData)
}
}
},
)
}
},
)
}
})
}
const frogsWithVitalSigns = getFrogsWithVitalSigns({
offset: 50,
})
.then((result) => {
console.log(result)
})
.catch((error) => {
console.error(error)
})
你可以在代码段中直观地看到有一些奇怪的结果。仅通过三个异步API调用,回调地狱就开始陷入与通常的上下方向相反的方向。有了promise,它不再成为问题,因为我们可以通过链接.then
的方法将代码保留在第一个处理程序的根目录中:
function getFrogsWithVitalSigns(params, callback) {
let frogIds, frogsListWithVitalSignsData
api
.fetchFrogs(params)
.then((frogs) => {
frogIds = frogs.map(({ id }) => id)
// The list of frogs did not include their health information, so lets fetch that now
return api.fetchFrogsVitalSigns(frogIds)
})
.then((frogsListWithEncryptedVitalSigns) => {
// The list of frogs health info is encrypted. Our friend texted us the secret key to use in this step. This is used to decrypt the list of frogs encrypted health information
return api.decryptFrogsListVitalSigns(
frogsListWithEncryptedVitalSigns,
'pepsi',
)
})
.then((data) => {
if (Array.isArray(data)) {
frogsListWithVitalSignsData = data
} else {
frogsListWithVitalSignsData = data.map(
({ vital_signs }) => vital_signs,
)
console.log(frogsListWithVitalSignsData)
}
})
.catch((error) => {
console.error(error)
})
})
}
const frogsWithVitalSigns = getFrogsWithVitalSigns({
offset: 50,
})
.then((result) => {
console.log(result)
})
.catch((error) => {
console.error(error)
})
在这个回调代码段中,如果我们仅嵌套几层,那么事情将变得更加难以管理。
仅通过查看代表此回调地狱的先前代码片段,我们就可以得出一系列由此而产生的危险问题,这些清单足以证明promise是该语言的不错补充:
代码开始向两个方向移动(从上到下,然后从左到右)
.then
链接Promise而解决的。当我们需要执行一系列异步任务时,承诺链就变得绝对有用。被链接的每个任务只能在上一个任务完成后立即开始,由.then
链的s 控制。
这些.then
块是在内部设置的,因此它们允许回调函数返回promise,然后将其应用于.then
链中的每个块.
.then
除了.catch
块带来的被拒绝的Promise外,您从中返回的任何东西最终都会变成一个正常的Promise。这是一个简短的示例:
const add = (num1, num2) => new Promise((resolve) => resolve(num1 + num2))
add(2, 4)
.then((result) => {
console.log(result) // result: 6
return result + 10
})
.then((result) => {
console.log(result) // result: 16
return result
})
.then((result) => {
console.log(result) // result: 16
})
JS中的Promise构造函数定义了几种静态方法,可用于从Promise中检查一个或者多个结果
当你想要累计一批异步操作并最终将它们的每一个值作为一个数组来接收时,满足此目标的Promise方法就是Promise.all
Promise.all
能够在所有操作成功结束时,搜集操作结构。这仅在此处类似于Promise.allSettled
。如果这些操作中的某一项或者多项失败,则Promise将拒绝并显示错误。最终,这会出现在.catch
Promise 链中。
从操作开始到完成的任何时候都可能发生Promise拒绝。如果在所有结果完成之前发生拒绝,那么未完成的结果将被终止,并且永远无法完成。换句话说,它是全有或全无的调用之一。
这是一个简单的代码示例,其中该Promise.all
方法使用getFrogs
和getLizards
,它们是promises。再将结果.then
存储到LocalStarage
之前,它将在处理程序中以数组形式检索结果:
const getFrogs = new Promise((resolve) => {
resolve([
{ id: 'mlo29naz', name: 'larry', born: '2016-02-22' },
{ id: 'lp2qmsmw', name: 'sally', born: '2018-09-13' },
])
})
const getLizards = new Promise((resolve) => {
resolve([
{ id: 'aom39d', name: 'john', born: '2017-08-11' },
{ id: '20fja93', name: 'chris', born: '2017-01-30' },
])
})
function addToStorage(item) {
if (item) {
let prevItems = localStorage.getItem('items')
if (typeof prevItems === 'string') {
prevItems = JSON.parse(prevItems)
} else {
prevItems = []
}
const newItems = [...prevItems, item]
localStorage.setItem('items', JSON.stringify(newItems))
}
}
let allItems = []
Promise.all([getFrogs, getLizards])
.then(([frogs, lizards]) => {
localStorage.clear()
frogs.forEach((frog) => {
allItems.push(frog)
})
lizards.forEach((lizard) => {
allItems.push(lizard)
})
allItems.forEach((item) => {
addToStorage(item)
})
})
.catch((error) => {
console.error(error)
})
console.log(localStorage.getItem('items'))
/*
result:
[{"id":"mlo29naz","name":"larry","born":"2016-02-22"},{"id":"lp2qmsmw","name":"sally","born":"2018-09-13"},{"id":"aom39d","name":"john","born":"2017-08-11"},{"id":"20fja93","name":"chris","born":"2017-01-30"}]
*/
每当可迭代的Promise中的一个Promise以该Promise的值或原因解析或拒绝时,此方法都会返回一个履行或拒绝的Promise。
这里是promise1
、promise2
和Promise.race
之间的有效方法的简单例子:
const promise1 = new Promise((resolve) => {
setTimeout(() => {
resolve('some result')
}, 200)
})
const promise2 = new Promise((resolve, reject) => {
reject(new Error('some promise2 error'))
})
Promise.race([promise1, promise2])
.then((result) => {
console.log(result)
})
.catch((error) => {
console.error(error)
})
它的结果将是:
由于另一个Promise被延迟了200毫秒,因此返回值最终成为了Promise拒绝。
Promise.allSettled
方法有些类似于Promise.all
共享一个相似的目标,除了在一个Promise失败时它不会立即拒绝产生错误,而是会返回一个Promise,这个Promise将会在所有给定的Promise都已解决或被拒绝后最终解决,并将结果累积到每个项目代表其promise操作的结果的数组。
这意味着您将总是以数组数据类型结束。下面是一个简单的例子:
const add = (num1, num2) => new Promise((resolve) => resolve(num1 + num2))
const multiply = (num1, num2) => new Promise((resolve) => resolve(num1 * num2))
const fail = (num1) =>
new Promise((resolve, reject) =>
setTimeout(() => reject(new Error('You, my friend, were too late')), 200),
)
const fail2 = (num1) =>
new Promise((resolve, reject) =>
setTimeout(
() => reject(new Error('Being late is never a good habit')),
100,
),
)
const promises = [add(2, 4), multiply(5, 5), fail('hi'), fail2('hello')]
Promise.allSettled(promises)
.then((result) => {
console.log(result)
})
.catch((error) => {
console.error(error)
})
Promise.any是添加到Promise
构造函数的提案,该提案目前处于TC39流程的第3阶段。Promise.any建议的是接受一个可迭代的Promise,并试图返回这与多数赞成接受或拒绝的Promise。这意味着如果有一个操作消耗了15个Promise, 而其中的14 个在解决一个Promise时就失败了,那么结果将Promise.any成为已解决的Promise的值:
const multiply = (num1, num2) => new Promise((resolve) => resolve(num1 * num2))
const fail = (num1) =>
new Promise((resolve, reject) =>
setTimeout(() => reject(new Error('You, my friend, were too late')), 200),
)
const promises = [
fail(2),
fail(),
fail(),
multiply(2, 2),
fail(2),
fail(2),
fail(2, 2),
fail(29892),
fail(2),
fail(2, 2),
fail('hello'),
fail(2),
fail(2),
fail(1),
fail(),
]
Promise.any(promises)
.then((result) => {
console.log(result) // result: 4
})
.catch((error) => {
console.error(error)
})
在https://github.com/tc39/proposal-promise-any了解更多信息。
add(5, 5).then(
function success(result) {
return result
},
function error(error) {
console.error(error)
},
)
add(5, 5)
.then(function success(result) {
return result
})
.catch(function(error) {
console.error(error)
})
但是,这两个例子并不完全相同,在变化2中,如果我们尝试在resolve处理程序中发生了错误,那么我们只要检查.catch
的内容有没有出错:
add(5, 5)
.then(function success(result) {
throw new Error("You aren't getting passed me")
})
.catch(function(error) {
// 错误在这儿停止了
})
在变化1中,如果我们试图抛出一个错误的处理程序,我们可能找不到错误所在:
add(5, 5).then(
function success(result) {
throw new Error("You aren't getting passed me")
},
function error(error) {
//难不成你希望这儿一直出错吗?
},
)