方法 手寫promise_手寫原始碼系列二——Promise相關方法
阿新 • • 發佈:2021-01-16
技術標籤:方法 手寫promise
歡迎關注微信公眾號——“較真的前端”,裡面有更多前端面試題和解答。
手寫Promise相關方法
Promise是面試中經常遇到的,如果面試中面試官問你Promise.all()
怎麼用,那你面試的崗位差不多是高階前端開發的崗位,但如果讓你手寫一個Promise.all()
那你面試的崗位應該就是資深/專家前端開發的崗位了
上期回顧
上期我們實現了函式的call()、bind()、apply()方法。
Promise.all()
先回顧一下Promise.all()的用法
Promise.all(iterable)
方法返回一個 Promise
例項,此例項在 iterable
promise
都“完成(resolved)”或引數中不包含 promise
時回撥完成(resolve);如果引數中 promise
有一個失敗(rejected),此例項回撥失敗(reject),失敗原因的是第一個失敗 promise
的結果。
例子如下:
var promise1 = Promise.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); }); // expected output: Array [3, 42, "foo"]
手寫實現Promise.all()方法
直接上程式碼:
Promise.myAll = function (iterators) { const promises = Array.from(iterators) const len = promises.length let count = 0 let resultList = [] return new Promise((resolve, reject) => { promises.forEach((p, index) => { Promise.resolve(p) .then((result) => { count++ resultList[index] = result if (count === len) { resolve(resultList) } }) .catch(e => { reject(e) }) }) }) }
核心思路:
Promise.myAll()
返回的肯定是一個promise
物件,所以可以直接寫一個return new Promise((resolve, reject) => {})
(這應該是一個慣性思維)- 遍歷傳入的引數,用
Promise.resolve()
將引數"包一層",使其變成一個promise
物件 - 關鍵點是何時"決議",也就是何時
resolve
出來,在這裡做了計數器(count
),每個內部promise物件決議後就將計數器加一,並判斷加一後的大小是否與傳入物件的數量相等,如果相等則呼叫resolve()
,如果任何一個promise物件失敗,則呼叫reject()
方法。
一些細節:
- 官方規定Promise.all()接受的引數是一個
可遍歷的
引數,所以未必一定是一個數組,所以用Array.from()
轉化一下 - 使用
for…of
進行遍歷,因為凡是可遍歷的
變數應該都是部署了iterator
方法,所以用for…of
遍歷最安全
Promise.race()
回顧一下race的用法
var promise1 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, 'one');
});
var promise2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, 'two');
});
Promise.race([promise1, promise2]).then(function(value) {
console.log(value);
// Both resolve, but promise2 is faster
});
// expected output: "two"
有了Promise.all()
的鋪墊,race就好寫多了。
Promise.myRace = function (iterators) {
return new Promise((resolve, reject) => {
for (let p of iterators) {
Promise.resolve(p)
.then((result) => {
resolve(result)
})
.catch(e => {
reject(e)
})
}
})
}
核心思路:
- 誰先決議那麼就返回誰,所以將all的計數器和邏輯判斷全部去除掉就可以了。
Promise.prototype.finally()
回顧一下正常用法。
finally()
方法返回一個Promise
。在promise結束時,無論結果是fulfilled或者是rejected,都會執行指定的回撥函式。這為在Promise
是否成功完成後都需要執行的程式碼提供了一種方式。
這避免了同樣的語句需要在then()
和catch()
中各寫一次的情況。
let isLoading = true;
fetch(myRequest).then(function(response) {
var contentType = response.headers.get("content-type");
if(contentType && contentType.includes("application/json")) {
return response.json();
}
throw new TypeError("Oops, we haven't got JSON!");
})
.then(function(json) { /* process your JSON further */ })
.catch(function(error) { console.log(error); })
.finally(function() { isLoading = false; });
手寫實現
Promise.prototype.myFinally = function finallyPolyfill(callback) {
return this.then(function(value) {
return Promise.resolve(callback()).then(function() {
return value;
});
}, function(reason) {
return Promise.resolve(callback()).then(function() {
Promise.reject(reason);
});
});
};
核心思路:
- 當前
this
指向的是當前Promise
物件,所以可以直接用this.then()
- then回撥中的兩個引數,一個是成功時的回撥,另一個是失敗是的回撥,利用這個兩個引數處理當前promise物件的兩種不同情況
- 無論如何都要呼叫傳入的callback函式,並且將當前promise的決議值繼續傳遞下去
一些細節:
callback傳入的有可能仍然是一個Promsie物件,如果真的是Promise物件,要等該promise決議之後才能執行之後then()方法,但是這個then()中拿到的是finally()之前的決議值,有種"決議值穿透"的感覺。
PS:我在網上找到了最權威的寫法,毫無破綻 https://github.com/matthew-andrews/Promise.prototype.finally/blob/master/finally.js