若是您不熟悉JavaScript,而且很难理解Promise的工做原理,但愿本文能帮助您更清楚地了解Promise。 话虽如此,本文针对的是那些对Promise不太熟悉的人。 这篇文章不会讨论使用async / await执行Promise(尽管它们在功能上是同样的,但在大多数状况下 async/await 才是真正的语法糖)。javascript
实际上,在JavaScript原生以前,承诺就已经存在了一段时间。例如,在promises成为原生以前实现该模式的两个库是Q和when。java
那么什么是Promise?Promise是JS对象,它们用于表示一个异步操做的最终完成 (或失败), 及其结果值.查看MDN 您能够经过使用回调方法或使用Promise执行异步操做来得到结果。可是二者之间有一些细微的差别。node
二者之间的主要区别在于,使用回调方法时,咱们一般只是将回调传递给一个函数,该函数将在完成时被调用以获取某些结果。可是,在Promise中,您将回调附加在返回的Promise对象上。 CallBacks:git
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:github
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的核心部分。api
因此,咱们为何须要JS中的Promise?数组
为了明白这个问题,咱们得先来聊聊为何在大多数的JS开发者中,仅仅使用CallBack的方法是远远不够的。promise
使用回调方法的一个常见问题是,当咱们最终不得不一次执行多个异步操做时,咱们很容易以所谓的回调地狱了结,这可能会成为噩梦,由于它致使难以管理且难读取。换句话说,这是每一个开发者的噩梦。异步
下面是一个简单的例子:async
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.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)
})
复制代码
在此处了解更多信息。
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) {
//难不成你但愿这儿一直出错吗?
},
)
复制代码
若是你有什么疑问欢迎在下面评论区留言!