一步一步了解Promise原理
?最近看到一篇有關promise的文章還不錯,想著翻譯過來看看,順便也算是對promise作個總結。
好了,開門見山,跟著我的腳步一起來學習吧。
一、最簡單的例子
1、先看例子1:
function doSomething(callback) {var value = 42;callback(value); }doSomething(function(value) {console.log('Got a value:' + value); });復制代碼這是一個傳入函數作為回調函數的函數,調用回調函數得到value值。
需要做些什么改變讓它看起來像‘promise’呢?來看例子2
2、例子2:
function doSomething() {return {then: function(callback) {var value = 42;callback(value);}}; } doSomething().then(function(value) {console.log('Got a value:' + value); });復制代碼例2是在例1的基礎上做修改:在函數體內直接返回一個對象,然后對象里面有then,其value為function。
使用上是在.then()里傳入一個回調函數。并且得到then中的value(42)。這使用看起來和promise好像一樣了。
promise有個重要的思想:Promises capture the notion of an eventual value into an object。這句話的意思是promise捕捉最終值傳入到一個對象中。
讀到這里也許有些困惑,沒關系,一起繼續探索強大有趣的事情吧!!!(老外的語言就是這么直白簡單hhhhhh...)
二、定義Promise類
1、現在開始,進入正題。定義一個簡單的Promise類。
function Promise(fn) {var callback = null;this.then = function(cb) {callback = cb;};function resolve(value) {callback(value);}fn(resolve); }function doSomething() {return new Promise(function(resolve) {var value = 42;resolve(value);}); }復制代碼來調用一下:
doSomething().then(function(res){console.log(res); })//來分析一下執行順序: // doSomething -> new Promise -> fn -> resolve -> callback復制代碼emm...執行到這里,報錯了??? callback是null?
為什么呢??因為callback是在then()方法中被賦值的,現在resolve()早于then()執行,callback還是null的狀態。
那怎么辦呢?
我們用setTimeout來解決這個問題。
function Promise(fn) {var callback = null;this.then = function(cb) {callback = cb;};function resolve(value) {// force callback to be called in the next// iteration of the event loop, giving// callback a chance to be set by then()setTimeout(function() {callback(value);}, 1);}fn(resolve); }復制代碼好了?現在我們再來分析下執行過程。
doSomething -> new Promise -> fn -> resolve -> then -> callback復制代碼這樣是順序能夠正常運行了,但是我覺得這個代碼糟糕透了
來下面的例子:
var promise = doSomething()setTimeout(function() {promise.then(function(value) {log("got a value", value);}); 復制代碼如果我們是這樣調用呢,是不是又要報錯了!
callback和then()先后被放入異步執行隊列中,根據先進先出原則,callback先執行,可此時callback?is not a function?but null.
我們的這個嘗試以失敗告終,接著來...
2、給Promise添加狀態
- A promise can be **pending** waiting for a value, or **resolved** with a value.
- Once a promise resolves to a value, it will always remain at that value and never resolve again.
這兩句話的意思是一個promise要么處于pending狀態(等待結果),要么已經resolved(得到結果),promise一旦resolved得到結果,它就一直保持這個結果不會再執行resolve。
(promise的reject稍后討論)
function Promise(fn) {var state = 'pending';var value;var deferred;function resolve(newValue) {value = newValue;state = 'resolved';if(deferred) {handle(deferred);}}function handle(onResolved) {if(state === 'pending') {deferred = onResolved;return;}onResolved(value);}this.then = function(onResolved) {handle(onResolved);};fn(resolve); } function doSomething() {return new Promise(function(resolve) {var value = 42;resolve(value);}); }function doSomethingElse() {return new Promise(function(resolve) {var value = 3;setTimeout(function(){resolve(value);},1);}); } 復制代碼我們來調用一下:
測試1: doSomething().then(function(value) {log("got a value", value); });//分析一下過程: doSomething -> Promise -> fn -> resolve -> then -> handle -> onResolved復制代碼測試2: var promise = doSomething(); setTimeout(function(){promise.then(function(value){log("got a value", value);}) }) //過程分析 doSomething -> Promise -> fn -> resolve -> then -> handle -> onResolved復制代碼測試3: var promiseElse = doSomethingElse(); promiseElse.then(function(value){log("got a value", value); }); //過程分析 doSomethingElse -> Promise -> fn -> then -> handle(deferred緩存onResolved) ->setTimeout異步隊列開始執行,resolve執行 -> handle(deferred)復制代碼以上測試無論何時調用then()和resolve(),根據state狀態的判斷,可以收放自如地完成同步異步函數執行。
With promises, the order in which we work with them doesn't matter. We are free to call `then()` and `resolve()` whenever they suit our purposes. This is one of the powerful advantages of capturing the notion of eventual results into an object顯然,到這里我們已經實現了Promise強大的pending-->resolved功能。
接下來我們要干什么呢?
三、鏈式Promises
平時我們對Promise的鏈式調用使用得很多吧,即使沒使用過鏈式調用那也一定看到過哦。
`then()` always returns a promise根據這個原則,我們稍作修改了代碼
function Promise(fn) {var state = 'pending';var value;var deferred = null;function resolve(newValue) {value = newValue;state = 'resolved';if(deferred) {handle(deferred);}}function handle(handler) {if(state === 'pending') {deferred = handler;return;}if(!handler.onResolved) {handler.resolve(value);return;}var ret = handler.onResolved(value);handler.resolve(ret);}this.then = function(onResolved) {return new Promise(function(resolve) {handle({onResolved: onResolved,resolve: resolve});});};fn(resolve); }復制代碼?這里我們注意到:then()返回以個新的Promise對象,這使得我們在實例化一個Promise對象之后調用then()時還可以接著調用then(),形成了鏈式調用。
下面我們來看下測試用例:
doSomething().then(function(result) {console.log('first result', result);return 88; }).then(function(secondResult) {console.log('second result', secondResult); }); // first result 42 // second result 88復制代碼1、參數可選
當然then()方法里面的參數是可選,我們也可能遇到這樣情況:
doSomething().then().then(function(result) {console.log('got a result', result); }); // got a result 42 //這里第一個then沒有回調函數 但是第二個then還是能得到結果 //執行順序: doSomething -> Promise -> fn -> resolve -> then -> handle -> handler.resolve(42) -> then -> handle -> handler.onResolve(42) -> handler.resolve(undefined)復制代碼這里的第一個then沒有傳入回調函數,所以直接執行handler.resolve()將value保存以待第二個then獲取。
2、在鏈中返回Promise
doSomething().then(function(result) {// doSomethingElse returns a promisereturn doSomethingElse(result); }).then(function(finalResult) {console.log("the final result is", finalResult); });復制代碼這里的finalResult按照預期的結果是返回了Promise實例,但是如果我們想要doSomethingElse中resolved值怎么辦?
修改一下resolve函數
function resolve(newValue) {if(newValue && typeof newValue.then === 'function') {newValue.then(resolve);return;}state = 'resolved';value = newValue;if(deferred) {handle(deferred);} }復制代碼我們保持resolve()中value值不是一個promise實例而是一個普通值,所以在value賦值之前再做一次.then的操作,直到得到value的值為一個普通值。
附上完整代碼,執行一遍。
function Promise(fn) {var state = 'pending';var value;var deferred = null;function resolve(newValue) {if (newValue && typeof newValue.then === 'function') {newValue.then(resolve);return;}state = 'resolved';value = newValue;if (deferred) {handle(deferred);}}function handle(handler) {if (state === 'pending') {deferred = handler;return;}if (!handler.onResolved) {handler.resolve(value);return;}var ret = handler.onResolved(value);handler.resolve(ret);}this.then = function (onResolved) {return new Promise(function (resolve) {handle({onResolved: onResolved,resolve: resolve});});};fn(resolve); }function doSomething() {return new Promise(function (resolve) {var value = 42;resolve(value);}); }function doSomethingElse(value) {return new Promise(function (resolve) {resolve("did something else with " + value);}); }doSomething().then(function (firstResult) {log("first result:", firstResult);return doSomethingElse(firstResult); }).then(function (secondResult) {log("second result:", secondResult); });//first result: 42//second result: did something else with 42 //執行順序分析: doSomething -> Promise -> fn -> resolve -> then -> Promise -> fn -> handle -> handler.onResolved -> handler.resolve(doSomethingElse(firstResult)) -> newValue.then -> Promise -> fn -> handle(這里的onResolved傳入的是上一個then的resolve) -> handler.onResolved 這里完成對上一個then的value賦值 -> handler-resolve -> then(這里開始第二個then)復制代碼這里過程有點繞,不過慢慢分析可以理清楚這中間value傳遞。
四、reject處理
終于要加上reject部分的處理啦?其實和resolve部分差不多。當程序運行錯誤時,我們需要獲知為什么產生了錯誤,那么在promise中我們知道錯誤的發生呢??
這時需要then的第二個回調函數!
function Promise(fn) {var state = 'pending';var value;var deferred = null;function resolve(newValue) {if(newValue && typeof newValue.then === 'function') {newValue.then(resolve, reject);return;}state = 'resolved';value = newValue;if(deferred) {handle(deferred);}}function reject(reason) {state = 'rejected';value = reason;if(deferred) {handle(deferred);}}function handle(handler) {if(state === 'pending') {deferred = handler;return;}var handlerCallback;if(state === 'resolved') {handlerCallback = handler.onResolved;} else {handlerCallback = handler.onRejected;}if(!handlerCallback) {if(state === 'resolved') {handler.resolve(value);} else {handler.reject(value);}return;}var ret = handlerCallback(value);handler.resolve(ret);}this.then = function(onResolved, onRejected) {return new Promise(function(resolve, reject) {handle({onResolved: onResolved,onRejected: onRejected,resolve: resolve,reject: reject});});};fn(resolve, reject); }復制代碼我們來調用一下:
function doSomething() {return new Promise(function(resolve, reject) {var result = somehowGetTheValue();if(result.error) {reject(result.error);} else {resolve(result.value);}}); }復制代碼As mentioned earlier, the promise will transition from **pending** to either **resolved** or **rejected**, never both. In other words, only one of the above callbacks ever gets called
上面doSomething調用時,我們發現Promise的回調方法中,如果有錯誤發生,那么調用reject();如果沒有錯誤發生,那么調用resolve()。Promise會從pending到resolved,或者從pending到rejected,只有這兩種情況,而且只能發生其一,也就是說要么成功,要么失敗。
意想不到的錯誤我們也要處理。
剛剛我們調用reject()方法是在我們能知道的情況下,但是萬一在promise中發生錯誤我們應該怎么辦呢?
這里有兩個采取捕捉錯誤的方法。
1、在resolve中可能發生錯誤進行捕捉
function resolve(newValue) {try {// ... as before} catch(e) {reject(e);} }復制代碼2、在handle方法中進行錯誤捕捉
function handle(deferred) {// ... as beforevar ret;try {ret = handlerCallback(value);} catch(e) {handler.reject(e);return;}handler.resolve(ret); }復制代碼文章到這里,reject的部分也完成了。如果你對done()、all()、race()等方法感興趣你可以去看promise的官方API。
github上有學習的代碼:https://github.com/Turboemily/learn_promise_development.git
以上內容來自文章:https://www.mattgreer.org/articles/promises-in-wicked-detail/#defining-the-promise-type。
總結
以上是生活随笔為你收集整理的一步一步了解Promise原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 聊聊flink的Async I/O
- 下一篇: 张帅用赢球庆生 搭档斯托瑟晋级澳网女双八