摘要:以上代碼,可以完美通過所有用例。在的函數中,為何需要這個同樣是因為規范中明確表示因此我們需要這樣的來確保只會執行一次。其他情況,直接返回以該值為成功狀態的對象。
Promise是前端面試中的高頻問題,我作為面試官的時候,問Promise的概率超過90%,據我所知,大多數公司,都會問一些關于Promise的問題。如果你能根據PromiseA+的規范,寫出符合規范的源碼,那么我想,對于面試中的Promise相關的問題,都能夠給出比較完美的答案。
我的建議是,對照規范多寫幾次實現,也許第一遍的時候,是改了多次,才能通過測試,那么需要反復的寫,我已經將Promise的源碼實現寫了不下七遍。
Promise的源碼實現/** * 1. new Promise時,需要傳遞一個 executor 執行器,執行器立刻執行 * 2. executor 接受兩個參數,分別是 resolve 和 reject * 3. promise 只能從 pending 到 rejected, 或者從 pending 到 fulfilled * 4. promise 的狀態一旦確認,就不會再改變 * 5. promise 都有 then 方法,then 接收兩個參數,分別是 promise 成功的回調 onFulfilled, * 和 promise 失敗的回調 onRejected * 6. 如果調用 then 時,promise已經成功,則執行 onFulfilled,并將promise的值作為參數傳遞進去。 * 如果promise已經失敗,那么執行 onRejected, 并將 promise 失敗的原因作為參數傳遞進去。 * 如果promise的狀態是pending,需要將onFulfilled和onRejected函數存放起來,等待狀態確定后,再依次將對應的函數執行(發布訂閱) * 7. then 的參數 onFulfilled 和 onRejected 可以缺省 * 8. promise 可以then多次,promise 的then 方法返回一個 promise * 9. 如果 then 返回的是一個結果,那么就會把這個結果作為參數,傳遞給下一個then的成功的回調(onFulfilled) * 10. 如果 then 中拋出了異常,那么就會把這個異常作為參數,傳遞給下一個then的失敗的回調(onRejected) * 11.如果 then 返回的是一個promise,那么需要等這個promise,那么會等這個promise執行完,promise如果成功, * 就走下一個then的成功,如果失敗,就走下一個then的失敗 */ const PENDING = "pending"; const FULFILLED = "fulfilled"; const REJECTED = "rejected"; function Promise(executor) { let self = this; self.status = PENDING; self.onFulfilled = [];//成功的回調 self.onRejected = []; //失敗的回調 //PromiseA+ 2.1 function resolve(value) { if (self.status === PENDING) { self.status = FULFILLED; self.value = value; self.onFulfilled.forEach(fn => fn());//PromiseA+ 2.2.6.1 } } function reject(reason) { if (self.status === PENDING) { self.status = REJECTED; self.reason = reason; self.onRejected.forEach(fn => fn());//PromiseA+ 2.2.6.2 } } try { executor(resolve, reject); } catch (e) { reject(e); } } Promise.prototype.then = function (onFulfilled, onRejected) { //PromiseA+ 2.2.1 / PromiseA+ 2.2.5 / PromiseA+ 2.2.7.3 / PromiseA+ 2.2.7.4 onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value; onRejected = typeof onRejected === "function" ? onRejected : reason => { throw reason }; let self = this; //PromiseA+ 2.2.7 let promise2 = new Promise((resolve, reject) => { if (self.status === FULFILLED) { //PromiseA+ 2.2.2 //PromiseA+ 2.2.4 --- setTimeout setTimeout(() => { try { //PromiseA+ 2.2.7.1 let x = onFulfilled(self.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { //PromiseA+ 2.2.7.2 reject(e); } }); } else if (self.status === REJECTED) { //PromiseA+ 2.2.3 setTimeout(() => { try { let x = onRejected(self.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }); } else if (self.status === PENDING) { self.onFulfilled.push(() => { setTimeout(() => { try { let x = onFulfilled(self.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }); }); self.onRejected.push(() => { setTimeout(() => { try { let x = onRejected(self.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }); }); } }); return promise2; } function resolvePromise(promise2, x, resolve, reject) { let self = this; //PromiseA+ 2.3.1 if (promise2 === x) { reject(new TypeError("Chaining cycle")); } if (x && typeof x === "object" || typeof x === "function") { let used; //PromiseA+2.3.3.3.3 只能調用一次 try { let then = x.then; if (typeof then === "function") { //PromiseA+2.3.3 then.call(x, (y) => { //PromiseA+2.3.3.1 if (used) return; used = true; resolvePromise(promise2, y, resolve, reject); }, (r) => { //PromiseA+2.3.3.2 if (used) return; used = true; reject(r); }); }else{ //PromiseA+2.3.3.4 if (used) return; used = true; resolve(x); } } catch (e) { //PromiseA+ 2.3.3.2 if (used) return; used = true; reject(e); } } else { //PromiseA+ 2.3.3.4 resolve(x); } } module.exports = Promise;
有專門的測試腳本可以測試所編寫的代碼是否符合PromiseA+的規范。
首先,在promise實現的代碼中,增加以下代碼:
Promise.defer = Promise.deferred = function () { let dfd = {}; dfd.promise = new Promise((resolve, reject) => { dfd.resolve = resolve; dfd.reject = reject; }); return dfd; }
安裝測試腳本:
npm install -g promises-aplus-tests
如果當前的promise源碼的文件名為promise.js
那么在對應的目錄執行以下命令:
promises-aplus-tests promise.js
promises-aplus-tests中共有872條測試用例。以上代碼,可以完美通過所有用例。
對上面的代碼實現做一點簡要說明(其它一些內容注釋中已經寫得很清楚):
onFulfilled 和 onFulfilled的調用需要放在setTimeout,因為規范中表示: onFulfilled or onRejected must not be called until the execution context stack contains only platform code。使用setTimeout只是模擬異步,原生Promise并非是這樣實現的。
在 resolvePromise 的函數中,為何需要usedd這個flag,同樣是因為規范中明確表示: If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored. 因此我們需要這樣的flag來確保只會執行一次。
self.onFulfilled 和 self.onRejected 中存儲了成功的回調和失敗的回調,根據規范2.6顯示,當promise從pending態改變的時候,需要按照順序去指定then對應的回調。
PromiseA+的規范(翻譯版)PS: 下面是我翻譯的規范,供參考
術語
promise 是一個有then方法的對象或者是函數,行為遵循本規范
thenable 是一個有then方法的對象或者是函數
value 是promise狀態成功時的值,包括 undefined/thenable或者是 promise
exception 是一個使用throw拋出的異常值
reason 是promise狀態失敗時的值
要求2.1 Promise States
Promise 必須處于以下三個狀態之一: pending, fulfilled 或者是 rejected
2.1.1.1 可以變成 fulfilled 或者是 rejected
2.1.2.1 不會變成其它狀態 2.1.2.2 必須有一個value值
2.1.3.1 不會變成其它狀態 2.1.3.2 必須有一個promise被reject的reason
概括即是:promise的狀態只能從pending變成fulfilled,或者從pending變成rejected.promise成功,有成功的value.promise失敗的話,有失敗的原因
2.2 then方法promise必須提供一個then方法,來訪問最終的結果
promise的then方法接收兩個參數
promise.then(onFulfilled, onRejected)
2.2.1.1 onFulfilled 必須是函數類型 2.2.1.2 onRejected 必須是函數類型
2.2.2.1 必須在promise變成 fulfilled 時,調用 onFulfilled,參數是promise的value 2.2.2.2 在promise的狀態不是 fulfilled 之前,不能調用 2.2.2.3 onFulfilled 只能被調用一次
2.2.3.1 必須在promise變成 rejected 時,調用 onRejected,參數是promise的reason 2.2.3.2 在promise的狀態不是 rejected 之前,不能調用 2.2.3.3 onRejected 只能被調用一次
2.2.6.1 如果promise變成了 fulfilled態,所有的onFulfilled回調都需要按照then的順序執行 2.2.6.2 如果promise變成了 rejected態,所有的onRejected回調都需要按照then的順序執行
promise2 = promise1.then(onFulfilled, onRejected);
2.2.7.1 onFulfilled 或 onRejected 執行的結果為x,調用 resolvePromise 2.2.7.2 如果 onFulfilled 或者 onRejected 執行時拋出異常e,promise2需要被reject 2.2.7.3 如果 onFulfilled 不是一個函數,promise2 以promise1的值fulfilled 2.2.7.4 如果 onRejected 不是一個函數,promise2 以promise1的reason rejected2.3 resolvePromise
resolvePromise(promise2, x, resolve, reject)
2.3.2.1 如果x是pending態,那么promise必須要在pending,直到 x 變成 fulfilled or rejected. 2.3.2.2 如果 x 被 fulfilled, fulfill promise with the same value. 2.3.2.3 如果 x 被 rejected, reject promise with the same reason.
2.3.3.1 let then = x.then. 2.3.3.2 如果 x.then 這步出錯,那么 reject promise with e as the reason.. 2.3.3.3 如果 then 是一個函數,then.call(x, resolvePromiseFn, rejectPromise) 2.3.3.3.1 resolvePromiseFn 的 入參是 y, 執行 resolvePromise(promise2, y, resolve, reject); 2.3.3.3.2 rejectPromise 的 入參是 r, reject promise with r. 2.3.3.3.3 如果 resolvePromise 和 rejectPromise 都調用了,那么第一個調用優先,后面的調用忽略。 2.3.3.3.4 如果調用then拋出異常e 2.3.3.3.4.1 如果 resolvePromise 或 rejectPromise 已經被調用,那么忽略 2.3.3.3.4.3 否則,reject promise with e as the reason 2.3.3.4 如果 then 不是一個function. fulfill promise with x.
雖然上述的promise源碼已經符合PromiseA+的規范,但是原生的Promise還提供了一些其他方法,如:
Promise.resolve()
Promise.reject()
Promise.prototype.catch()
Promise.prototype.finally()
Promise.all()
Promise.race()
下面具體說一下每個方法的實現:
Promise.resolve
Promise.resolve(value) 返回一個以給定值解析后的Promise 對象.
如果 value 是個 thenable 對象,返回的promise會“跟隨”這個thenable的對象,采用它的最終狀態
如果傳入的value本身就是promise對象,那么Promise.resolve將不做任何修改、原封不動地返回這個promise對象。
其他情況,直接返回以該值為成功狀態的promise對象。
Promise.resolve = function (param) { if (param instanceof Promise) { return param; } return new Promise((resolve, reject) => { if (param && param.then && typeof param.then === "function") { setTimeout(() => { param.then(resolve, reject); }); } else { resolve(param); } }); }
thenable對象的執行加 setTimeout的原因是根據原生Promise對象執行的結果推斷的,如下的測試代碼,原生的執行結果為: 20 400 30;為了同樣的執行順序,增加了setTimeout延時。
測試代碼:
let p = Promise.resolve(20); p.then((data) => { console.log(data); }); let p2 = Promise.resolve({ then: function(resolve, reject) { resolve(30); } }); p2.then((data)=> { console.log(data) }); let p3 = Promise.resolve(new Promise((resolve, reject) => { resolve(400) })); p3.then((data) => { console.log(data) });
Promise.reject
Promise.reject方法和Promise.resolve不同,Promise.reject()方法的參數,會原封不動地作為reject的理由,變成后續方法的參數。
Promise.reject = function (reason) { return new Promise((resolve, reject) => { reject(reason); }); }
Promise.prototype.catch
Promise.prototype.catch 用于指定出錯時的回調,是特殊的then方法,catch之后,可以繼續 .then
Promise.prototype.catch = function (onRejected) { return this.then(null, onRejected); }
Promise.prototype.finally
不管成功還是失敗,都會走到finally中,并且finally之后,還可以繼續then。并且會將值原封不動的傳遞給后面的then.
Promise.prototype.finally = function (callback) { return this.then((value) => { return Promise.resolve(callback()).then(() => { return value; }); }, (err) => { return Promise.resolve(callback()).then(() => { throw err; }); }); }
Promise.all
Promise.all(promises) 返回一個promise對象
如果傳入的參數是一個空的可迭代對象,那么此promise對象回調完成(resolve),只有此情況,是同步執行的,其它都是異步返回的。
如果傳入的參數不包含任何 promise,則返回一個異步完成.
promises 中所有的promise都promise都“完成”時或參數中不包含 promise 時回調完成。
如果參數中有一個promise失敗,那么Promise.all返回的promise對象失敗
在任何情況下,Promise.all 返回的 promise 的完成狀態的結果都是一個數組
Promise.all = function (promises) { return new Promise((resolve, reject) => { let index = 0; let result = []; if (promises.length === 0) { resolve(result); } else { function processValue(i, data) { result[i] = data; if (++index === promises.length) { resolve(result); } } for (let i = 0; i < promises.length; i++) { //promises[i] 可能是普通值 Promise.resolve(promises[i]).then((data) => { processValue(i, data); }, (err) => { reject(err); return; }); } } }); }
測試代碼:
var promise1 = new Promise((resolve, reject) => { resolve(3); }) var promise2 = 42; var promise3 = new Promise(function(resolve, reject) { setTimeout(resolve, 100, "foo"); }); Promise.all([promise1, promise2, promise3]).then(function(values) { console.log(values); //[3, 42, "foo"] },(err)=>{ console.log(err) }); var p = Promise.all([]); // will be immediately resolved var p2 = Promise.all([1337, "hi"]); // non-promise values will be ignored, but the evaluation will be done asynchronously console.log(p); console.log(p2) setTimeout(function(){ console.log("the stack is now empty"); console.log(p2); });
Promise.race
Promise.race函數返回一個 Promise,它將與第一個傳遞的 promise 相同的完成方式被完成。它可以是完成( resolves),也可以是失敗(rejects),這要取決于第一個完成的方式是兩個中的哪個。
如果傳的參數數組是空,則返回的 promise 將永遠等待。
如果迭代包含一個或多個非承諾值和/或已解決/拒絕的承諾,則 Promise.race 將解析為迭代中找到的第一個值。
Promise.race = function (promises) { return new Promise((resolve, reject) => { if (promises.length === 0) { return; } else { for (let i = 0; i < promises.length; i++) { Promise.resolve(promises[i]).then((data) => { resolve(data); return; }, (err) => { reject(err); return; }); } } }); }
測試代碼:
Promise.race([ new Promise((resolve, reject) => { setTimeout(() => { resolve(100) }, 1000) }), undefined, new Promise((resolve, reject) => { setTimeout(() => { reject(100) }, 100) }) ]).then((data) => { console.log("success ", data); }, (err) => { console.log("err ",err); }); Promise.race([ new Promise((resolve, reject) => { setTimeout(() => { resolve(100) }, 1000) }), new Promise((resolve, reject) => { setTimeout(() => { resolve(200) }, 200) }), new Promise((resolve, reject) => { setTimeout(() => { reject(100) }, 100) }) ]).then((data) => { console.log(data); }, (err) => { console.log(err); });
謝謝您花費寶貴的時間閱讀本文,如果本文給了您一點幫助或者是啟發,那么不要吝嗇你的贊和Star哈,您的肯定是我前進的最大動力。https://github.com/YvetteLau/...
關注小姐姐的公眾號,和小姐姐一起學前端。文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/108987.html
摘要:的翻譯文檔由的維護很多人說,阮老師已經有一本關于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。 JavaScript Promise 迷你書(中文版) 超詳細介紹promise的gitbook,看完再不會promise...... 本書的目的是以目前還在制定中的ECMASc...
摘要:手寫一款符合規范的長篇預警有點長,可以選擇性觀看。初始狀態是,狀態可以有或者不能從轉換為或者從轉換成即只要由狀態轉換為其他狀態后,狀態就不可變更。 手寫一款符合Promise/A+規范的Promise 長篇預警!有點長,可以選擇性觀看。如果對Promise源碼不是很清楚,還是推薦從頭看,相信你認真從頭看到尾,并且去實際操作了,肯定會有收獲的。主要是代碼部分有點多,不過好多都是重復的,不...
摘要:如果實現滿足所有要求,則實現可能允許。本條款允許使用特定于實現的方法來采用已知一致承諾的狀態。接下來根據規范進行手寫實現注釋偷懶就將對應的規范標注出來,其實基本上就是對著規范實現。 如果要手寫實現promise,那么先看看promise/A+規范,再來實現,將會事半功倍。那么我先翻譯一下Promise/A+規范中的內容。 術語 1.1 promise 是一個帶有符合此規范的the...
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。寫一個符合規范并可配合使用的寫一個符合規范并可配合使用的理解的工作原理采用回調函數來處理異步編程。 JavaScript怎么使用循環代替(異步)遞歸 問題描述 在開發過程中,遇到一個需求:在系統初始化時通過http獲取一個第三方服務器端的列表,第三方服務器提供了一個接口,可通過...
摘要:為的項,取出來的分別為和,所以上的和方法,調用的是中的方法,實質是往各自的回調列表中添加回調函數。進度回調函數數組。參數為異步對象的索引值,參數為對應的上下文數組,即或,為對應的回調函數數組,即或。 Deferred 模塊也不是必備的模塊,但是 ajax 模塊中,要用到 promise 風格,必需引入 Deferred 模塊。Deferred 也用到了上一篇文章《讀Zepto源碼之C...
閱讀 675·2021-09-30 09:47
閱讀 2869·2021-09-04 16:40
閱讀 853·2019-08-30 13:18
閱讀 3447·2019-08-29 16:22
閱讀 1551·2019-08-29 12:36
閱讀 583·2019-08-29 11:11
閱讀 1474·2019-08-26 13:47
閱讀 1127·2019-08-26 13:32