若是您不熟悉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) {
//難不成你但願這兒一直出錯嗎?
},
)
複製代碼
若是你有什麼疑問歡迎在下面評論區留言!