摘要:前言在寫這個之前,希望你已經對中的很熟悉了,概念性和基礎的東西就不再講了,不懂的同學可以去看看阮一峰老師的教程我主要按以下個步驟來一步一步實現,異步的實現我放在了后面,所以前面幾步暫不考慮實現一個基本的實現的鏈式調用處理函數的參數是實例的情
前言
在寫這個promise之前,希望你已經對es6中的Promise很熟悉了,概念性和基礎的東西就不再講了,不懂的同學可以去看看阮一峰老師的es6教程. 我主要按以下5個步驟來一步一步實現,異步的實現我放在了后面,所以前面幾步暫不考慮
實現一個基本的MyPromise
實現then的鏈式調用
處理reolve函數的參數是MyPromise實例的情況以及處理then方法中前一個回調函數返回的也是一個MyPromise實例的情況
實現異步的MyPromise
完善MyPromise的其它方法
1.實現一個基本的MyPromise/* * 這里我將promise的3個狀態分別定義為: pending, resolved, rejected * 其中fn必須是個函數, 必須通過new來使用 */ function MyPromise(fn) { if (!(this instanceof MyPromise)) { throw new TypeError("MyPromise must be constructed via new"); } if (typeof fn !== "function") { throw new TypeError("MyPromise constructor argument is not a function"); } this.state = "pending"; // 出初始化狀態 this.value = undefined; // 初始化一個值, 用來存儲resolve或者reject的值 // 執行 fn 方法 executeFn(fn, this); } MyPromise.prototype.then = function(onFullfilled, onRejected) { var res = undefined; var cb = this.state === "resolved" ? onFullfilled : onRejected; res = cb(this.value); } // 執行 fn 方法 function executeFn(fn, promise) { var done = false; // 聲明一個變量, 防止resolve, reject連續調用 try { fn(function _resolve(value) { if(done) return; done = true; resolve(promise, value); }, function _reject(reason) { if(done) return; done = true; reject(promise, reason); }); } catch(err) { if(!done) { done = true; reject(promise, err); } } } function resolve(promise, value) { promise.state = "resolved"; promise.value = value; } function reject(promise, error) { promise.state = "rejected"; promise.value = error; }
這樣就實現了一個基本版的MyPromise,調用方法同Promise,如下:
new MyPromise((resolve, reject) => { // resolve("resolved"); reject("rejected"); }).then(res => { console.log(">>> res", res); }, err => { console.log(">>> err", err); });2.實現then的鏈式調用
原生Promise支持鏈式調用,并且then方法會返回一個新的Promise實例,第一個回調函數完成以后,會將返回結果作為參數,傳入第二個回調函數,所以我們可以修改下then方法,其余不變
MyPromise.prototype.then = function(onFullfilled, onRejected) { var self = this; var res = undefined; var cb = this.state === "resolved" ? onFullfilled : onRejected; var newPromise = new MyPromise(function(_resolve, _reject) { try { res = cb(self.value); _resolve(res); } catch(err) { _reject(err); } }); return newPromise; }
這樣的話,鏈式調用也就實現了,測試了下,沒啥問題
new MyPromise((resolve, reject) => { resolve("resolved"); // reject("rejected"); }).then(res => { console.log(">>> res", res); return "res1"; }, err => { console.log(">>> err", err); return "err1"; }).then(res2 => { console.log(">>> res2", res2); }, err2 => { console.log(">>> err2", err2); });3. 處理reolve函數的參數是MyPromise實例的情況以及處理then方法中前一個回調函數返回的也是一個MyPromise實例的情況
resolve函數的參數除了正常的值以外,還可能是另一個MyPromise實例,如下,這個時候p1的狀態決定了p2的狀態
const p1 = new MyPromise(function(resolve, reject) { // ... }); const p2 = new MyPromise(function(resolve, reject) { // ... resolve(p1); });
同樣,在then方法中,前一個回調函數有可能返回的也是一個MyPromise對象,這時后一個回調函數就會等待該MyPromise對象的狀態發生變化,才會被調用,例如:
const p1 = new MyPromise(function(resolve, reject) { // ... }); const p2 = new MyPromise(function(resolve, reject) { // ... resolve("p2 resolved"); }); p2.then(res1 => { console.log(">>> res1", res1); return p1; }, err1 => { console.log(">>> err1", err1); }).then(res2 => { console.log(">>> res2", res2); }, err2 => { console.log(">>> err2", err2); })
了解以上后,我們來實現它
function resolve(promise, value) { if(!handlePromise(promise, value)) return; // 增加這行 promise.state = "resolved"; promise.value = value; } /* * 增加一個函數用來處理返回值或者resolve的參數是promise的情況, 最后的返回值起個標識作用 */ function handlePromise(promise, value) { if(value === promise) { reject(promise, "A promise cannot be resolved with itself"); return; } if(value && (typeof value === "object" || typeof value === "function")) { var then = value.then; if(typeof then === "function") { executeFn(then.bind(value), promise); return; } } return true; } // 同時then中增加一行代碼 MyPromise.prototype.then = function(onFullfilled, onRejected) { var self = this; var res = undefined; var cb = this.state === "resolved" ? onFullfilled : onRejected; var newPromise = new MyPromise(function(_resolve, _reject) { if(self.state === "pending") return; // 增加這行 try { res = cb(self.value); _resolve(res); } catch(err) { _reject(err); } }); return newPromise; }
到這里,就解決了以上2個問題,測試一下
const p = new MyPromise((resolve, reject) => { // resolve("resolve a promise"); reject("reject a promise"); }); // resolve參數是MyPromise實例 new MyPromise((resolve, reject) => { resolve(p); // reject("rejected"); }).then(res => { console.log(">>> res", res); return "res1"; }, err => { console.log(">>> err", err); return "err1"; }).then(res2 => { console.log(">>> res2", res2); }, err2 => { console.log(">>> err2", err2); }); // then第一個方法返回的是MyPromise實例 new MyPromise((resolve, reject) => { resolve("resolved"); // reject("rejected"); }).then(res => { console.log(">>> res", res); return p; }, err => { console.log(">>> err", err); return "err1"; }).then(res2 => { console.log(">>> res2", res2); }, err2 => { console.log(">>> err2", err2); });4. 實現異步的MyPromise
經過前面3步,MyPromise的已經實現了大半,接著我們來實現異步的MyPromise. 既然是異步,我們并不知道它什么時候結束,但是我們可以將它的異步回調存入一個數組,待它結束后執行它,好吧,其實就是觀察者模式了
首先在構造函數中加上一句
function MyPromise(fn) { // ...這里省略,加上下面這行 this.callbacks = []; // 存儲異步的回調方法 // 執行 fn 方法 executeFn(fn, this); }
然后在resolve和reject方法中分別加上
function resolve(promise, value) { // ...這里省略,加上下面幾句 promise.callbacks.forEach(function(cb) { cb(); }); } function reject(promise, error) { // ...這里省略,加上下面幾句 promise.callbacks.forEach(function(cb) { cb(); }); }
最后在then方法中,將異步的回調發存入數組中
MyPromise.prototype.then = function(onFullfilled, onRejected) { // ...這里省略,不變 var newPromise = new MyPromise(function(_resolve, _reject) { // 狀態為pending時,將回調存入數組,因為then中方法也是異步執行 // 所以用setTimeout,同時直接return if(self.state === "pending") { self.callbacks.push(function() { setTimeout(function() { // 這里需要再次判斷 cb = self.state === "resolved" ? onFullfilled : onRejected; try { res = cb(self.value); _resolve(res); } catch(err) { _reject(err); } }); }); return; } // then中是異步執行 setTimeout(function() { try { res = cb(self.value); _resolve(res); } catch(err) { _reject(err); } }); }); return newPromise; }
到這里,異步的MyPromise也就實現了,then方法代碼有點亂,我們整理下
MyPromise.prototype.then = function(onFullfilled, onRejected) { // 這里刪除了一部分代碼 var self = this; var newPromise = new MyPromise(function(_resolve, _reject) { if(self.state === "pending") { self.callbacks.push(function() { // 同時將這部分的代碼抽成了以下方法 handleResolved(self, onFullfilled, onRejected, _resolve, _reject); }); return; } handleResolved(self, onFullfilled, onRejected, _resolve, _reject); }); return newPromise; } function handleResolved(promise, onFullfilled, onRejected, _resolve, _reject) { setTimeout(function() { var res = undefined; var cb = promise.state === "resolved" ? onFullfilled : onRejected; // 需要對cb進行判斷 if(typeof cb !== "function") { if(promise.state === "resolved") { _resolve(promise.value); } else { _reject(promise.value); } return; } try { res = cb(promise.value); _resolve(res); } catch(err) { _reject(err); } }); }
測試如下, 當然沒啥問題了
const p = new MyPromise((resolve, reject) => { setTimeout(() => { resolve("resolve a promise"); // reject("reject a promise"); }, 2 * 1000); }); new MyPromise((resolve, reject) => { resolve(p); // reject("rejected"); }).then(res => { console.log(">>> res", res); return "res1"; }, err => { console.log(">>> err", err); return "err1"; }).then(res2 => { console.log(">>> res2", res2); }, err2 => { console.log(">>> err2", err2); });
到這里,一個大概的MyPromise也就基本實現完成了,整理后的完整代碼如下
function MyPromise(fn) { if (!(this instanceof MyPromise)) { throw new TypeError("MyPromise must be constructed via new"); } if (typeof fn !== "function") { throw new TypeError("MyPromise constructor argument is not a function"); } this.state = "pending"; // 初始化狀態 this.value = undefined; // 初始化一個值, 用來存儲resolve或者reject的值 this.callbacks = []; // 存儲異步的回調方法 // 執行 fn 方法 executeFn(fn, this); } MyPromise.prototype.then = function(onFullfilled, onRejected) { var self = this; var newPromise = new MyPromise(function(_resolve, _reject) { if(self.state === "pending") { self.callbacks.push(function() { handleResolved(self, onFullfilled, onRejected, _resolve, _reject); }); return; } handleResolved(self, onFullfilled, onRejected, _resolve, _reject); }); return newPromise; } function handleResolved(promise, onFullfilled, onRejected, _resolve, _reject) { setTimeout(function() { var res = undefined; var cb = promise.state === "resolved" ? onFullfilled : onRejected; if(typeof cb !== "function") { if(promise.state === "resolved") { _resolve(promise.value); } else { _reject(promise.value); } return; } try { res = cb(promise.value); _resolve(res); } catch(err) { _reject(err); } }); } // 執行 fn 方法 function executeFn(fn, promise) { var done = false; // 聲明一個變量, 防止resolve, reject連續調用 try { fn(function _resolve(value) { if(done) return; done = true; resolve(promise, value); }, function _reject(reason) { if(done) return; done = true; reject(promise, reason); }); } catch(err) { if(!done) { done = true; reject(promise, err); } } } function resolve(promise, value) { if(!handlePromise(promise, value)) return; promise.state = "resolved"; promise.value = value; promise.callbacks.forEach(function(cb) { cb(); }); } function reject(promise, error) { promise.state = "rejected"; promise.value = error; promise.callbacks.forEach(function(cb) { cb(); }); } // 用來處理返回值或者resolve的參數是promise的情況, 最后的返回值起個標識作用 function handlePromise(promise, value) { if(value === promise) { reject(promise, "A promise cannot be resolved with itself"); return; } if(value && (typeof value === "object" || typeof value === "function")) { var then = value.then; if(typeof then === "function") { executeFn(then.bind(value), promise); return; } } return true; }5. 最后來實現下MyPromise的其它方法
MyPromise.prototype.catch = function(onRejected) { return this.then(null, onRejected); } // 只要不是pending狀態都會執行 MyPromise.prototype.finally = function(cb) { return this.then( function(value) { return MyPromise.resolve(cb()).then(function() { return value; }); }, function(err) { return MyPromise.resolve(cb()).then(function() { throw err; }); } ); } MyPromise.resolve = function(val) { return new MyPromise(function(resolve, reject) { resolve(val); }); } MyPromise.reject = function(err) { return new MyPromise(function(resolve, reject) { reject(err); }); } /* * all方法用于將多個 MyPromise 實例,包裝成一個新的 MyPromise 實例 * 只有全部實例都resolved,才會resolve; 只要其中一個rejected,就會reject * 參數可以不是數組,但必須具有 Iterator 接口, 同時里面的值可能也不是promise實例 */ MyPromise.all = function(promiseArr) { var args = [].slice.call(promiseArr); return new MyPromise(function(resolve, reject) { var arr = []; var resolveCount = 0; var argsLen = args.length; for(var i = 0; i < argsLen; i++) { handle(i, args[i]); } function handle(index, val) { MyPromise.resolve(val).then(function(value) { arr[index] = value; if(++resolveCount === argsLen) { resolve(arr); } }, reject); } }); } /* * race方法與all方法類似,只要其中一個實例狀態發生改變resolved / rejected即可 * 參數可以不是數組,但必須具有 Iterator 接口, 同時里面的值可能也不是promise實例 */ MyPromise.race = function(promiseArr) { var args = [].slice.call(promiseArr); return new MyPromise(function(resolve, reject) { for(var i = 0; i < args.length; i++) { MyPromise.resolve(args[i]).then(resolve, reject); } }); }
至此Promise的實現就算完成了,完整代碼的地址點這里
最后說點題外話,在面試的過程中,經常會遇見面試官要求現場實現一個Promise,看了這篇文章后希望對你有所幫助,已經能夠實現一個Promise了,而對于面試中其他的promise的相關問題能夠輕松應對
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/99981.html
摘要:上網查了一下好像是標準第二步開寫構造函數什么都不想,先寫一個構造函數,就叫把。對構造函數再次做修改。并且可以一個值。給下一個繼續調用。應該是一個新的。最后的版本總結后來去看了看別人實現的方法。 我能不能在不看別人怎么實現promise的情況下,自己實現一個promise? 都8102年為什么還要寫promise實現? ? 年前和年后面試了幾家公司, 雖然都掛了… 但是都談到了一個...
摘要:本文僅限瀏覽器環境測試,環境可能會不一致狀態一個實例只能處于三種狀態中的一種。每次創建的實例都會處于狀態,并且只能由變為或狀態。可以認為在實現里與中的都為解決程序。 前言 Promise作為ES6極為重要的一個特性,將我們從無限的回調地獄中解脫出來,變為鏈式的編寫回調,大大提高的代碼的可讀性。 使用Promise是極為簡單的,但只停留在會使用階段還是會讓我們不知不覺踩到一些坑的。本文會...
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。寫一個符合規范并可配合使用的寫一個符合規范并可配合使用的理解的工作原理采用回調函數來處理異步編程。 JavaScript怎么使用循環代替(異步)遞歸 問題描述 在開發過程中,遇到一個需求:在系統初始化時通過http獲取一個第三方服務器端的列表,第三方服務器提供了一個接口,可通過...
摘要:意味著操作成功完成。狀態的對象可能觸發狀態并傳遞一個值給相應的狀態處理方法,也可能觸發失敗狀態并傳遞失敗信息。測試用例測試用例方法返回一個帶有拒絕原因參數的對象。 Promise基本用法 Promise 對象是一個代理對象,被代理的值在Promise對象創建時可能是未知的。 它允許你為異步操作的成功和失敗分別綁定相應的處理方法(handlers)。 這讓異步方法可以像同步方法那樣返回值...
摘要:鏈式調用在的使用中,我們一定注意到,是可以鏈式調用的很顯然,要實現鏈式調用,方法的返回值也必須是一個對象,這樣才能再次在后面調用。一種情況下,前一個的或者的返回值是普通的對象,這種情況下我們目前的可以正確處理。 本文同步自我的個人博客: http://mly-zju.github.io/ 眾所周知javascript語言的一大特色就是異步,這既是它的優點,同時在某些情況下也帶來了一些的...
摘要:所以,這篇文章我會帶大家從零開始,手寫一個基本能用的。首先,規定對象是一個構造函數,用來生成實例。然后,這個構造函數接受一個函數作為參數,該函數的兩個參數分別是和。對象通過自身的狀態,來控制異步操作。 剛開始寫前端的時候,處理異步請求經常用callback,簡單又順手。后來寫著寫著就拋棄了callback,開始用promise來處理異步問題。promise寫起來確實更加優美,但由于缺乏...
閱讀 3077·2021-09-22 15:20
閱讀 2600·2019-08-30 15:54
閱讀 1966·2019-08-30 14:06
閱讀 3114·2019-08-30 13:05
閱讀 2457·2019-08-29 18:36
閱讀 568·2019-08-29 15:10
閱讀 523·2019-08-29 11:17
閱讀 818·2019-08-28 18:11