因為 JavaScript 是單線程語言,所以同步代碼一次只能執(zhí)行一行。這就意味著同步代碼的運行時間超過瞬間的話,它將停止其余代碼的運行,直到完成運行為止。為了防止運行時間不確定的代碼阻止其他代碼的運行,我們需要使用異步代碼。
Promise
為此,我們可以在代碼中使用 Promise。Promise 表示流程運行時間不確定并且結(jié)果可能是成功或失敗的對象。在 JavaScript 中創(chuàng)建一個 promises,我們使用 Promise 對象的構(gòu)造函數(shù)來創(chuàng)建 promise。Promise 構(gòu)造函數(shù)接受一個擁有 resolve 和 reject 參數(shù)的執(zhí)行函數(shù)。兩個參數(shù)也都是函數(shù),它們讓我們可以回調(diào) promise 完成(成功調(diào)用得到返回值)或者拒絕(返回錯誤值并標(biāo)記 promise 失敗)。函數(shù)的返回值被忽略。因此,promise 只能返回 promise。
例如,我們在 JavaScript 定義一個承諾像下面的代碼:
const promise = new Promise((resolve, reject) => { setTimeout(() => resolve('abc'), 1000); });
上面的代碼會創(chuàng)建一個一秒后返回 abc 的 promise。因為我們運行的 setTimeout 是在 executor 函數(shù)內(nèi)一秒后返回值為 abc 的完成 promise,所以它是異步代碼。我們無法在 setTimeout 的回調(diào)函數(shù)中返回值 abc,因此我們必須調(diào)用 resolve('abc') 來獲取解析后的值。我們可以使用 then 函數(shù)來訪問已完成 promise 的返回值。then 函數(shù)接受一個回調(diào)函數(shù),該回調(diào)函數(shù)將已完成的 promise 的返回值作為參數(shù)。你可以得到你想要的值。例如,我們可以這么做:
const promise = new Promise((resolve, reject) => { setTimeout(() => resolve('abc'), 1000); }); promise.then((val) => { console.log(val); })
當(dāng)我們運行上方代碼,我們應(yīng)該可以得到記錄 abc。正如我們看到的,promise 會在其完成后調(diào)用 resolve 函數(shù)時提供值。
一個 promise 有三種狀態(tài)。初始狀態(tài),promise 既沒有成功也沒有失敗。完成狀態(tài),意味著操作成功完成。或者是失敗狀態(tài),意味著 promise 操作失敗。
一個掛起的 promise 可以通過返回值完成或者返回錯誤失敗。當(dāng) promise 完成后,then 函數(shù)將得到相應(yīng)的返回值,并傳遞給 then 函數(shù)的回調(diào)函數(shù)來調(diào)用。如果 promise 失敗后,我們可以選擇使用 catch 函數(shù)來捕獲錯誤,該函數(shù)還可以傳遞給回調(diào)函數(shù)一個錯誤。then 和 catch 都返回一個 promise,因此它們可以一起鏈?zhǔn)绞褂谩?/p>
例如,我們可以這么寫:
const promise = (num) => { return new Promise((resolve, reject) => { setTimeout(() => { if (num === 1) { resolve('resolved') } else { reject('rejected') } }, 1000); }); } promise(1) .then((val) => { console.log(val); }) .catch((error) => { console.log(error); })promise(2) .then((val) => { console.log(val); }) .catch((error) => { console.log(error); })
在上面的代碼中,我們有一個函數(shù) promise,它返回一個 JavaScript promise,其在 num 為 1 時以 resolved 值完成承諾,在 num 不為 1 時通過錯誤 rejected 拒絕承諾。因此我們運行:
promise(1) .then((val) => { console.log(val); }) .catch((error) => { console.log(error); })
然后 then 函數(shù)運行,由于 num 為 1,promise(1) 函數(shù)調(diào)用會返回 promise 完成,并且解析后的值在 val 中是可用的。因此當(dāng)我們運行 console.log(val),我們會得到 resolved。當(dāng)我們運行下面的代碼:
promise(2) .then((val) => { console.log(val); }) .catch((error) => { console.log(error); })
catch 會運行,因為 promise(2) 函數(shù)調(diào)用失敗返回 promise,并且被拒絕的錯誤值可用并設(shè)置為錯誤。因此我們運行 console.log(error) 會得到 rejected 輸出。
一個 JavaScript promise 對象有一下屬性:length 和 prototype。 length 是構(gòu)造器參數(shù)的數(shù)量,其設(shè)置為 1,也總是只有一個。prototype 屬性表示 promise 對象的原型。
promise 還有一個 finally 方法無論 promise 完成還是失敗都運行的代碼。finally 方法接受一個回調(diào)函數(shù)作為參數(shù),可以運行任何你想運行的代碼, 并且無論 promise 運行結(jié)果如何,都可以執(zhí)行。例如,我們運行:
Promise.reject('error') .then((value) => { console.log(value); }) .catch((error) => { console.log(error); }) .finally(() => { console.log('finally runs'); })
然后我們得到 error 和 finally runs 的記錄,因為原始的 promise 得到 error 而拒絕。然后運行 finally 方法中的所有代碼。
使用 promise 最主要的好處是編寫的異步代碼,我們可以使用 promise 順序運行它們。為此,我們可以使用 then 函數(shù)鏈?zhǔn)?promise。then 函數(shù)在 promise 完成后接收一個回調(diào)函數(shù)并運行它。在 promise 拒絕后,它還接受第二個參數(shù)。鏈?zhǔn)绞褂?promise,我們必須將其第一個回調(diào)函數(shù) then 函數(shù)返回另外一個 promise。如果我們不想將另外一個 promise 鏈接到現(xiàn)有的 promise,我們可以返回其他值,就像沒有一樣。我們可以返回一個值,該值將在下一個 then 函數(shù)中可獲取到。它還可以拋出一個錯誤。然后 then 返回的 promise 將被拒絕,并拋出錯誤。它還可以返回已經(jīng)完成或拒絕的 promise,擋在其后鏈接的 then 函數(shù)時將獲得其完成后的值,或者 catch 函數(shù)的回調(diào)中獲得錯誤原因。
例如,我們可以這樣寫:
Promise.resolve(1) .then(val => { console.log(val); return Promise.resolve(2) }) .then(val => { console.log(val); })Promise.resolve(1) .then(val => { console.log(val); return Promise.reject('error') }) .then(val => { console.log(val); }) .catch(error => console.log(error));Promise.resolve(1) .then(val => { console.log(val); throw new Error('error'); }) .then(val => { console.log(val); }) .catch(error => console.log(error));
在第一個例子中,我們鏈?zhǔn)秸{(diào)用 promise,并且都 resolve 一個值。所有的 promise 都準(zhǔn)備返回為值。在第二個和最后一個例子中,我們拒絕了第二個 promise 或拋出一個錯誤。它們都做了同樣的事情。第二個 promise 被拒絕了,并且錯誤原因會被回調(diào)函數(shù) catch 函數(shù)記錄下來。我們還可以鏈接掛起狀態(tài)的 promise,如下方代碼所示:
const promise1 = new Promise((resolve, reject) => { setTimeout(() => resolve(1), 1000); }); const promise2 = new Promise((resolve, reject) => { setTimeout(() => resolve(2), 1000); }); promise1 .then(val => { console.log(val); return promise2; }) .then(val => { console.log(val); }) .catch(error => console.log(error));
回調(diào)函數(shù) then 函數(shù)返回了 promise2,這是一個掛起狀態(tài)的 promise。
方法
JavaScript 的 promise 有以下方法。
Promise.all (可迭代對象)
Promise.all 接受一個可迭代對象,該對象允許我們在某些計算機上并行運行多個 promise,并在其他計算機上連續(xù)運行多個 promise。這對于運行多個不依賴于彼此的值的 promise 非常方便。它接受一個包含 promise 的列表 (通常是一個數(shù)組) 的可迭代對象,然后返回一個 Promise,這個 Promise 在可迭代對象中的 promise 被解析時解析。
例如,我們像下面這樣寫代碼,使用 Promise.all 來運行多個 promise:
const promise1 = Promise.resolve(1); const promise2 = 2; const promise3 = new Promise((resolve, reject) => { setTimeout(() => resolve(3), 1000); }); Promise.all([promise1, promise2, promise3]) .then((values) => { console.log(values); });
如果我們運行了上方的代碼,然后 console.log 應(yīng)該會記錄下 [1,2,3]。如我們所見,只有所有的 promise 都完成后返回它的解析值。如果其中的拒絕了,我們不會得到任何解析值。相反,我們將得到由被拒絕的 promise 返回的任何錯誤值。它將會在第一個被拒絕的 promise 處停止,并且發(fā)送值給回調(diào)函數(shù) catch 函數(shù)。例如,如果我們這樣:
const promise1 = Promise.resolve(1); const promise2 = Promise.reject(2); const promise3 = new Promise((resolve, reject) => { setTimeout(() => reject(3), 1000); }); Promise.all([promise1, promise2, promise3]) .then((values) => { console.log(values); }) .catch(error => { console.log(error); });
然后我們可以在回調(diào)函數(shù) catch 函數(shù)的 console.log 中得到兩個記錄。
Promise.allSettled
Promise.allSettled 返回一個 promise,該 promise 的解析在所有的 promise 解析完或拒絕后。它接受帶有一組 promise 的可迭代對象,例如,一個 promise 數(shù)組。返回的 promise 的解析值是每個 promise 的最終狀態(tài)的數(shù)組。例如,假設(shè)我們有:
const promise1 = Promise.resolve(1); const promise2 = Promise.reject(2); const promise3 = new Promise((resolve, reject) => { setTimeout(() => reject(3), 1000); }); Promise.allSettled([promise1, promise2, promise3]) .then((values) => { console.log(values); })
若我們運行上方代碼,我們將獲得一個包含三個條目的數(shù)組,每個條目都是一個對象,該對象有已經(jīng)完成 promise 的 status 和 value 屬性以及被拒絕的 promise 的 status 和 reason 屬性。例如,上面的代碼會記錄 {status: “fulfilled”, value: 1},{status: “rejected”, reason: 2},{status: “rejected”, reason: 3}。 fulfilled 狀態(tài)記錄的是成功的 promise,rejected 狀態(tài)為被拒絕的 promise。
Promise.race
Promise.race 方法返回一個 promise,該 promise 會解析首先完成的 promise 的解析值。它接受一個帶有 promise 集合的可迭代對象,例如,一個 promise 數(shù)組。如果傳入的可迭代對象為空,則返回的 promise 將一直掛起。若可迭代對象包含一個或多個非 promise 值或者已經(jīng)完成的 promise,Promise.race 將會返回這些條目中的第一個。例如,我們有:
const promise1 = Promise.resolve(1); const promise2 = Promise.resolve(2); const promise3 = new Promise((resolve, reject) => { setTimeout(() => resolve(3), 1000); }); Promise.race([promise1, promise2, promise3]) .then((values) => { console.log(values); })
然后我們看到 1 會被記錄 。那是因為 promise1 是第一個被解析的,那是因為它是在下一行運行之前就被解析了。同樣,如果我們的數(shù)組中有一個非 promise 值作為參數(shù)進(jìn)行傳入,如下代碼所示:
const promise1 = 1; const promise2 = Promise.resolve(2); const promise3 = new Promise((resolve, reject) => { setTimeout(() => resolve(3), 1000); }); Promise.race([promise1, promise2, promise3]) .then((values) => { console.log(values); })
然后我們會得到相同的記錄,因為它是我們傳遞給 Promise.race 方法的數(shù)組中的非 promise 值。同步代碼始終運行在異步代碼之前,而不論同步代碼在哪里。如果我們有:
const promise1 = new Promise((resolve, reject) => { setTimeout(() => resolve(1), 2000); }); const promise2 = new Promise((resolve, reject) => { setTimeout(() => resolve(2), 1000); }); const promise3 = 3; Promise.race([promise1, promise2, promise3]) .then((values) => { console.log(values); })
然后我們記錄下 3,因為 setTimeout 將回調(diào)函數(shù)放入隊列中以便稍后運行,所以它將比同步代碼更晚執(zhí)行。
最后,如果我們有:
const promise1 = new Promise((resolve, reject) => { setTimeout(() => resolve(1), 2000); }); const promise2 = new Promise((resolve, reject) => { setTimeout(() => resolve(2), 1000); }); Promise.race([promise1, promise2]) .then((values) => { console.log(values); })
然后我們在控制臺中得到記錄 2,因為在一秒解析的 promise 要比兩秒解析的 promise 更早解析。
Promise.reject
Promise.reject 返回一個因某種原因拒絕的 promise。拒絕帶有 Error 的實例對象的 promise 非常有用。例如,如果我們有以下代碼:
Promise.reject(new Error('rejected')) .then((value) => { console.log(value); }) .catch((error) => { console.log(error); })
然后我們得到 rejected 記錄。
Promise.resolve
Promise.resolve 返回一個已解析為傳入 resolve 函數(shù)參數(shù)的值的 promise。我們也可以傳遞一個帶有 then 屬性的對象,它的值是 promise 的回調(diào)函數(shù)。如果該值具有 then 方法,則將使用 then 函數(shù)完成 promise。也就是說,then 函數(shù)值的第一個參數(shù)與 resolve 相同,以及第二個參數(shù)與 reject 相同。例如,我們可以編寫如下代碼:
Promise.resolve(1) .then((value) => { console.log(value); })
然后我們得到 1 記錄,因為 1 是我們傳遞給 resolve 函數(shù)來返后具有解析值 1 的承諾的值。
如果我們傳入的對象內(nèi)部帶有 then 方法,如下代碼所示:
Promise.resolve({ then(resolve, reject) { resolve(1); } }) .then((value) => { console.log(value); })
然后我們得到記錄的值 1。這是因為 Promise.resolve 函數(shù)將運行 then 函數(shù),設(shè)置為 “then” 屬性的函數(shù)的 “resolve” 參數(shù)將被假定為承諾中稱為 “resolve” 函數(shù)的函數(shù)。并將該函數(shù)的 resolve 參數(shù)設(shè)置為 then 屬性可以看作 promise 中一個叫做 resolve 函數(shù)。如果我們將傳入 then 中的對象替換為 inject 函數(shù),然后我們就可以得到被拒絕的 promise。代碼如下所示:
Promise.resolve({ then(resolve, reject) { reject('error'); } }) .then((value) => { console.log(value); }) .catch((error) => { console.log(error); })
在上面的代碼中,我們會得到 error 記錄,這是因為 promise 被拒絕了。
Async 和 Await
使用 async 和 await,我們可以縮短 promise 代碼。使用 async 和 await 之前前,我們必須得用 then 函數(shù)并且在 then 函數(shù)中放入回調(diào)函數(shù)作為所有的參數(shù)。這就使得我們有很多 promise 時代碼冗長至極。相反,我們可以使用 async 和 await 語法來替代 then 函數(shù)以及相關(guān)回調(diào)。例如,我們可將以下代碼縮短為:
const promise1 = new Promise((resolve, reject) => { setTimeout(() => resolve(1), 2000); }); const promise2 = new Promise((resolve, reject) => { setTimeout(() => resolve(2), 1000); }); promise1 .then((val1) => { console.log(val1); return promise2; }) .then((val2) => { console.log(val2); })
寫成:
const promise1 = new Promise((resolve, reject) => { setTimeout(() => resolve(1), 2000); }); const promise2 = new Promise((resolve, reject) => { setTimeout(() => resolve(2), 1000); }); (async () => { const val1 = await promise1; console.log(val1) const val2 = await promise2; console.log(val2) })()
我們使用 await 來替換 then 和回調(diào)函數(shù)。然后,我們就可以將每個 promise 的解析值分配為變量。注意,如果我們?yōu)?promise 代碼使用 await,那么我們必須像上例那樣添加 async 到函數(shù)簽名中。為了捕獲錯誤,我們使用 catch 子句取代鏈?zhǔn)?catch 函數(shù)。另外,我們沒有在底部鏈?zhǔn)秸{(diào)用 finally 函數(shù)以在 promise 結(jié)束時運行代碼,而是在 catch 子句后使用 finally 子句。
例如,我們可以這樣寫:
const promise1 = new Promise((resolve, reject) => { setTimeout(() => resolve(1), 2000); }); const promise2 = new Promise((resolve, reject) => { setTimeout(() => reject('error'), 1000); }); (async () => { try { const val1 = await promise1; console.log(val1) const val2 = await promise2; console.log(val2) } catch (error) { console.log(error) } finally { console.log('finally runs'); }})()
在上面的代碼中,我們獲得了分配給變量的 promise 的解析值,而不是在 then 函數(shù)的回調(diào)中獲取值,例如在 const response = await promise1 上面的一行。另外,我們使用 try...catch...finally 塊來捕獲被拒絕的 promise 的錯誤,以及 finally 子句替代 finally 函數(shù),其無論 promise 執(zhí)行結(jié)果如何,該代碼都可以運行。
想其他使用 promise 的函數(shù)一樣,async 函數(shù)始終返回 promise,并且不能返回其他任何東西。在上面的示例中,我們證明了與使用帶有回調(diào)函數(shù)作為參數(shù)傳遞的 then 函數(shù)相比,我們可以更短的方式使用 promise。
結(jié)束語
使用 promise,讓我們編寫異步代碼更容易。promise 是表示一個處理的運行時間不確定并且結(jié)果會成功也會失敗的對象。在 JavaScript 中創(chuàng)建一個 promises,我們使用 Promise 對象,該對象是用于創(chuàng)建 promise 的構(gòu)造函數(shù)。
Promise 構(gòu)造函數(shù)接受一個擁有 resolve 和 reject 參數(shù)的執(zhí)行函數(shù)。兩個參數(shù)都是函數(shù),它們讓我們可以回調(diào) promise 完成(成功調(diào)用得到返回值)或者拒絕(返回錯誤值并標(biāo)記 promise 失敗)。 The 函數(shù)的返回值被忽略。因此,promise 只能返回 promise。
因為 promise 返回 promise,所以 promise 是可鏈?zhǔn)秸{(diào)用。promise 的 then 函數(shù)可以拋出一個錯誤,返回解析值,或者其他 promise(掛起、已完成或已拒絕的 promise)。