摘要:說明狀態改變的調用是同步于的。如果在構造函數的回調函數中或的回調函數中發生了異常,返回的會自動。避免了發送重復的請求。
什么是Promise
Promise代理了一個可能要在未來才能到達的值[[PromiseValue]]。Promise的一個最重要的特點是,你可以通過then來指定當[[PromiseValue]]到來時(或到來失敗時)調用的handler。
Promise的4種狀態fulfilled - 成功,[[PromiseValue]]是成功獲取到的值
rejected - 失敗,[[PromiseValue]]是失敗的原因
pending - [[PromiseValue]]還沒有到達
settled - [[PromiseValue]]已經有結果(fulfilled或rejected)
創建Promise 方式1:new Promise(executor)new Promise( /* executor */ function(resolve, reject) { ... } );
傳入Promise()的參數叫做executor,它封裝了獲取[[PromiseValue]]的過程。
在初始化Promise的過程中,executor被Promise內部代碼執行,并給executor傳入2個參數:resolve函數, reject函數。
MDN文檔:The executor function is executed immediately by the Promise implementation, passing resolve and reject functions (the executor is called before the Promise constructor even returns the created object).
即使在executor中調用了resolve或reject,也會先執行完當前的executor函數體(不能像return一樣直接退出函數體)
var p1 = new Promise(function (res, rej) { console.log("before res"); res("ok!"); console.log("after res"); }); console.log("after p1 init, p1:", p1); // before res // after res 它被輸出說明:即使在executor中調用了resolve或reject,也會先執行完當前的executor函數體,而不像return那樣立即退出函數 // after p1 init, p1: Promise { "ok!" } 它在"before/after res"以后才被輸出說明:Promise在初始化過程中就會同步地調用executor(called synchronously)
executor的resolve或reject執行以后,Promise的狀態就立刻改變(change synchronously)。
var p1 = new Promise(function (res, rej) { setImmediate(function() { res("haha"); console.log("after res", p1); // executor的res函數執行完畢以后,p1狀態已經變為fulfilled }); }); console.log("after init", p1); // 由于executor的res或rej函數還未執行,p1處于pending狀態 // after init Promise {方式2:Promise.resolve(value)} // after res Promise { "haha" }
Promise.resolve(value);
返回一個fulfilled的Promise,[[PromiseValue]]為value。
Promise.resolve(promise);
直接返回參數promise。
Promise.resolve(thenable);
將thenable轉換為Promise,Promise的狀態和[[PromiseValue]]跟隨thenable。thenable會在后文討論。
方式3:Promise.reject(reason)返回一個rejected的Promise。
Promise.reject(reason)就是下面代碼的語法糖形式:
new Promise(function(resolve, reject){ reject(reason); });
它沒有Promise.resolve(something)這么復雜,不管傳入什么,它直接將參數reason作為reject原因,返回一個rejected Promise,即使你閑著沒事干傳一個Promise進去:
var p = Promise.resolve("res"); Promise.reject(p) .then((val) => { console.log("111", val); }, (err) => { console.log("222", err === p); }); // 222 true
為了方便debug,最好傳入Error實例。Promise的核心方法:then(onFulfilled[, onRejected]) 先說說then模式
then模式:你先把成功和失敗時要調用的handler傳給then函數,等到時機成熟以后(進入settled狀態以后),then函數就幫你調用合適的那個handler。存在一個這樣的then函數的對象叫做Thenable對象。
// 一個簡單的Thenable對象 var thenable = { then: function (onFulfilled, onRejected) { // setTimeout模擬一個需要花2秒的異步過程 setTimeout(function () { var num = Math.random(); if (num > 0.5) { onFulfilled(num); } else { onRejected(num); } }, 2000); } } // 使用方式 thenable.then( function (result) { console.log("get result:", result); }, function (err) { console.log("get error:", err); });
then模式類似于我們經常使用的Callback模式。說回Promise的then方法
Promise的then方法其實就是在普通的then模式的基礎上增加了鏈式調用的功能:then函數返回Promise對象,前一個Promise對象進入settled狀態以后才調用下一個then函數的handler。
Promise.prototype.then()涉及2個Promise對象:
調用then方法的Promise對象,這里用p1表示
調用then以后返回的Promise對象,這里用p2表示
p2 = p1.then(onFulfilled, onRejected);
then的作用就是,立即返回一個pending狀態的Promise:p2,并在p1進入settled狀態以后自動幫你調用handler:
如果進入fulfilled狀態(成功),自動調用onFulfilled
如果進入rejected狀態(失敗),自動調用onRejected
在調用完handler以后,會根據handler的返回值觸發p2的狀態改變:
如果handler返回一個普通值val,p2狀態立即(synchronously)變化:pending-->fulfilled,且p2的[[PromiseValue]]為val。
如果handler中throw一個錯誤err,p2狀態立即(synchronously)變化:pending-->rejected,且p2的[[PromiseValue]]為err。
如果handler返回一個settled的Promise對象temp,p2狀態立即(synchronously)變化:pending-->與temp相同的狀態,且p2的[[PromiseValue]]與temp的[[PromiseValue]]相同。
如果handler返回一個pending的Promise對象temp,p2的狀態不立即改變,而是等到temp進入settled狀態以后,p2的狀態再(異步地)改變:pending-->與temp相同的狀態,且p2的[[PromiseValue]]與temp的[[PromiseValue]]相同。
在這里我們只用關注p2是如何改變的,在后文我會解釋p2是什么時候改變的(同步還是異步)以及p2的handler是什么時候調用的。
舉個例子:
Promise.resolve("result").then(onFulfilled1, onRejected1).then(onFulfilled2, onRejected2);
這等價于:
var p1 = Promise.resolve("result"); var p2 = p1.then(onFulfilled1, onRejected1); var p3 = p2.then(onFulfilled2, onRejected2);
因為p1是fulfilled狀態,所以p1的成功handler——onFulfilled1被調用。
如果onFulfilled1返回普通值(不是Promise),那么p2的狀態變化:pending-->fulfilled,且p2的[[PromiseValue]]為onFulfilled1的返回值。接下來p2的成功handler——onFulfilled2被調用,且傳入onFulfilled2的參數為p2的[[PromiseValue]]。
如果onFulfilled1返回的是Promise,那么p2的狀態和[[PromiseValue]]都跟隨這個被返回的Promise。onFulfilled2將在p2 fulfilled以后被調用,onRejected2將在p2 rejected以后被調用,傳入的參數都是p2的[[PromiseValue]]。
依此類推,p3的狀態和[[PromiseValue]]都取決于onFulfilled2/onRejected2的返回值。
handler何時被調用
handler的調用異步于then的調用。
A異步于B的意思:A與B在JavaScript消息隊列中屬于不同的消息。當前消息的調用棧完全退出以后,Event loop再處理下一個消息。Event loop處理完B消息以后可能要再處理0個或多個消息才能處理到A。
如果p1是通過new Promise(executor)的方式得到,那么除了滿足第一條以外,p1 handler的調用還異步于executor中resolve()、reject()的調用。
如果Promise是由then返回的:
var p1 = Promise.resolve("haha"); var p2 = p1.then(p1_handler); p2.then(p2_handler)
那么除了滿足第一條以外,如我在之前討論then的時候所說:
如果p1_handler產生同步的結果(包括返回普通值、拋出異常、返回settled Promise),則p2狀態立即變化,且p2_handler立即調用。這兩者都是同步于p1_handler進行的。
如果p1_handler產生異步的結果(返回pending Promise,temp),則p2狀態隨著temp自動改變,且p2_handler在p2狀態改變以后自動調用。這兩者不僅異步于p1_handler進行,而且異步于temp的狀態改變。
接下來我們一個一個地討論。
1. handler的調用異步于then的調用在調用then為p1指定handler以后,并不會立即觸發handler的調用,而是向JavaScript消息隊列中增加一個消息,然后繼續執行then之后的代碼。等到then所在的執行棧完全彈出,Event loop再處理下一個消息。處理完若干個消息以后,Event loop處理到handler的消息。處理這個消息的時候,先檢查p1是否為settled,如果是,則調用對應的handler。
即使p1在調用then時就是settled狀態,handler的調用也是異步的。p1在調用then時是pending狀態的話就更不用說了。
var p1 = Promise.resolve("haha"); // p1在調用then時就是settled狀態 p1.then((val) => { // 在nextTick以后,p1 handler才調用 console.log("in p1 handler, p1:", p1); }); console.log("after then called, p1:", p1); process.nextTick(() => { console.log("nextTick, p1:", p1); }); // after then called, p1: Promise { "haha" } // nextTick, p1: Promise { "haha" } // in p1 handler, p1: Promise { "haha" }
這也是為什么then返回的p2(在剛被返回的時候)必定處于pending狀態。因為p1 handler的調用異步于p1的產生(也就異步于p2的產生)。p2需要等待p1 handler異步調用并返回結果才能改變狀態,因此在handler被調用以前,p2都是pending狀態:
var p1 = Promise.resolve("haha"); console.log("p1", p1); var p2 = p1.then((val) => { console.log("in p1 handler"); return "xixi"; }); console.log("p2", p2); setImmediate(() => { console.log("setImmediate p2", p2); }); // p1 Promise { "haha" } // p2 Promise {2. handler的調用異步于executor中resolve()、reject()的調用} // in p1 handler // setImmediate p2 Promise { "xixi" }
如果p1是通過new Promise(executor)的方式得到,那么除了滿足第一條以外,p1 handler的調用還異步于executor中resolve()、reject()的調用。
也就是說resolve()、reject()的調用并不會立即觸發handler的調用,而是向JavaScript消息隊列中增加一個消息,等待Event loop處理到這個消息。處理這個消息的時候會調用handler。
例子(用node.js運行):
var global_val = "old value"; var p1 = new Promise((res) => { setTimeout(function () { res("haha"); console.log(p1); // p1的狀態立刻改變 console.log("immediately", global_val); // 但是此時p1的handler還沒有調用 process.nextTick(function () { console.log("nextTick", global_val); // 此時p1的handler還是沒有調用 }); setImmediate(function() { console.log("setImmediate", global_val); // 此時p1的handler已經調用 }); }, 1000); }); var p2 = p1.then(() => { // p1的handler console.log("in p1 handler"); global_val = "new value"; }); // Promise { "haha" } // immediately old value // nextTick old value // in p1 handler // setImmediate new value為什么handler要異步于executor的resolve()、reject()調用
因為在executor的resolve()、reject()的調用以后可能還有其他代碼要同步執行(當前handler還沒有結束)。前一個handler都還沒有執行完,自然不應該開始下一個handler的執行。(handler的執行不應該嵌套,而應該串行)
比如在上面global_val的例子中,傳入setTimeout的函數就是一個handler,調用res("haha")的時候這個handler還有很多代碼要執行。那么下一個handler(p1的handler)不應該打斷這些代碼的執行。
3. 如果Promise是由then返回的如果Promise是由then返回的:
var p1 = Promise.resolve("haha"); var p2 = p1.then(p1_handler); p2.then(p2_handler)
那么除了滿足第一條以外,如我在之前討論then的時候所說:
如果p1_handler產生同步的結果(包括返回普通值、拋出異常、返回settled Promise),則p2狀態立即變化,且p2_handler立即調用。這兩者都是同步于p1_handler進行的。
如果p1_handler產生異步的結果(返回pending Promise,temp),則p2狀態隨著temp自動改變,且p2_handler在p2狀態改變以后自動調用。這兩者不僅異步于p1_handler進行,而且異步于temp的狀態改變。
這里給出一個測試代碼供大家自行驗證,注釋中有說明,并且可以通過注釋/解注釋來修改p1 handler的返回結果。
// 如果p1_handler產生同步的結果(包括返回普通值、拋出異常、返回settled Promise),則p2狀態立即變化,且p2_handler立即調用。這兩者都是同步于p1_handler進行的。 // 如果p1_handler產生異步的結果(返回pending Promise,temp),則p2狀態隨著temp自動改變,且p2_handler在p2狀態改變以后自動調用。這兩者不僅異步于p1_handler進行,而且異步于temp的狀態改變。 var p1 = Promise.resolve("haha"); var p2 = p1.then((val) => { console.log("in p1 handler. p1:", p1, "p2", p2, "p3:", p3); process.nextTick(function () { // 當p1 handler產生同步的結果時,p2 handler在nextTick之前就被調用,且p2在nextTick時已經settled。說明p2狀態改變、p2 handler的調用是同步于p1 handler的。 // 當p1 handler產生異步的結果時,p2 handler在nextTick之后才被調用,且p2在nextTick時依然pending。說明p2狀態改變、p2 handler的調用是異步于p1 handler的。 console.log("p1 handler nextTick. p1:", p1, "p2", p2, "p3:", p3); }); // throw "err!"; // return Promise.resolve("heihei"); // return Promise.reject("heihei"); // return new Promise(res => { // temp Promise // setTimeout(function () { // res("heihei"); // process.nextTick(function () { // // p2在nextTick時依然是pending,說明p2的狀態改變異步于temp的狀態改變 // console.log("resolve nextTick. p1:", p1, "p2", p2, "p3:", p3); // }); // }, 1000); // }); return "heihei"; }); var p3 = p2.then(val => { console.log("in p2 success handler. p1:", p1, "p2", p2, "p3:", p3); return "xixi"; }, err => { console.log("in p2 error handler. p1:", p1, "p2", p2, "p3:", p3); return "hoho"; }); process.nextTick(function () { // 它先于"in p1 handler"輸出可以說明handler是異步于then調用的 console.log("after then called. p1:", p1, "p2", p2, "p3:", p3); });異常處理
Promise rejected 后,將跳至帶有拒絕回調的下一個 then()(或具有相同功能的 catch())。如果有then(func1, func2),則 func1 或 func2 中的一個將被調用,而不可能二者均被調用。但如果是 then(func1).catch(func2),則有可能兩者均被調用(func1 rejected時)。
asyncThing1().then(function() { return asyncThing2(); }).then(function() { return asyncThing3(); }).catch(function(err) { return asyncRecovery1(); }).then(function() { return asyncThing4(); }, function(err) { return asyncRecovery2(); }).catch(function(err) { console.log("Don"t worry about it"); }).then(function() { console.log("All done!"); })
藍線表示 fulfilled 的 promise 路徑,紅路表示 rejected 的 promise 路徑。
如果在promise構造函數的回調函數中或then的回調函數中發生了 JavaScript 異常(throw Errow),返回的promise會自動reject。
var jsonPromise = new Promise(function(resolve, reject) { // JSON.parse throws an error if you feed it some // invalid JSON, so this implicitly rejects: resolve(JSON.parse("This ain"t JSON")); }); jsonPromise.then(function(data) { // This never happens: console.log("It worked!", data); }).catch(function(err) { // Instead, this happens: console.log("It failed!", err); })最佳實踐:緩存Promise——重復利用異步操作的結果
var storyPromise; function getChapter(i) { storyPromise = storyPromise || getJSON("story.json"); return storyPromise.then(function(story) { return getJSON(story.chapterUrls[i]); }) } // and using it is simple: getChapter(0).then(function(chapter) { console.log(chapter); return getChapter(1); }).then(function(chapter) { console.log(chapter); })
假設我們要分別獲取story的各個章節(用getChapter函數),每個章節所在的URL存儲在story.json的chapterUrls數組中。
我們不需要每次要獲取章節的時候都先獲取一次story.json在拿到章節所在URL,更好的做法是:調用一次getJSON("story.json")就將這個Promise緩存到storyPromise中,將來需要請求story.json的時候只需要重復利用這個fulfilled的storyPromise。避免了發送重復的HTTP請求。
Promise一旦settled,[[PromiseValue]]不會再改變,我們可以將它看作一個定值,多次使用。最佳實踐:一個 Promise fulfilled 以后再執行下一個 Promise
因為Promise對象無法被外部改變(無論是意外地還是惡意地),我們可以安全地將這個對象交給其他庫使用,而不用擔心庫會修改到Promise的結果。
除了手動寫then回調來依次執行Promise以外,對于一個數組的任務,我們可以利用array.reduce的循環來創建then回調:
// Loop through our chapter urls story.chapterUrls.reduce(function(sequence, chapterUrl) { // 上一個Promise fulfilled以后才執行下一個getJSON // 從而保證每一個章節是順序請求的,從而在頁面中是順序顯示的 return sequence.then(function() { return getJSON(chapterUrl); }).then(function(chapter) { addHtmlToPage(chapter.html); }); }, Promise.resolve())參考資料
https://developers.google.com...
https://developer.mozilla.org...
http://liubin.org/promises-book/
Node 定時器詳解講述了process.nextTick()和Promise回調函數(microtask)的執行順序。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/88449.html
摘要:忍者級別的函數操作對于什么是匿名函數,這里就不做過多介紹了。我們需要知道的是,對于而言,匿名函數是一個很重要且具有邏輯性的特性。通常,匿名函數的使用情況是創建一個供以后使用的函數。 JS 中的遞歸 遞歸, 遞歸基礎, 斐波那契數列, 使用遞歸方式深拷貝, 自定義事件添加 這一次,徹底弄懂 JavaScript 執行機制 本文的目的就是要保證你徹底弄懂javascript的執行機制,如果...
摘要:的翻譯文檔由的維護很多人說,阮老師已經有一本關于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。 JavaScript Promise 迷你書(中文版) 超詳細介紹promise的gitbook,看完再不會promise...... 本書的目的是以目前還在制定中的ECMASc...
摘要:安裝然后在的配置文件加入入口文件引入這樣就可以啦,還是可以減少很多代碼量的。是參數,等同于執行正常。這個包很簡單,就是引用了和,然后生產環境把它們編譯到目錄下,做了映射,供使用。 引入 這個問題是對自己的發問,但我相信會有很多跟我一樣的同學。對于 babel 的使用,近半年來一直停留在與 webpack 結合使用,以及在瀏覽器開發環境下。導致很多 babel 的包,我都不清楚他們是干嘛...
摘要:前端工程師自檢清單對于,掌握其語法和特性是最基本的,但是這些只是應用能力,最終仍舊考量仍然是計算機體系的理論知識,所以數據結構,算法,軟件工程,設計模式等基礎知識對前端工程師同樣重要,這些知識的理解程度,可以決定你在前端工程師這條路上能走多 2019前端工程師自檢清單 對于JavaScript,掌握其語法和特性是最基本的,但是這些只是應用能力,最終仍舊考量仍然是計算機體系的理論知識,所...
摘要:簡單的說,即將到來的標準指出是一個,所以作為一個,必須可以被子類化。保護還是子類化這是個問題我真的希望我能創建一個忠實的給及以下。 原文地址:http://blog.getify.com/promis... 如果你需要趕上我們關于Promise的進度,可以看看這個系列前兩篇文章深入理解Promise五部曲--1.異步問題和深入理解Promise五部曲--2.控制權轉移問題。 Promi...
閱讀 2703·2021-11-25 09:43
閱讀 2085·2021-11-24 09:39
閱讀 1954·2021-11-17 09:33
閱讀 2750·2021-09-27 14:11
閱讀 1840·2019-08-30 15:54
閱讀 3224·2019-08-26 18:27
閱讀 1264·2019-08-23 18:00
閱讀 1810·2019-08-23 17:53