概要
本文主要介紹了Promise上定義的api的特性,以及手寫如何實現這些特性。目的是把學習過程中的產出以部落格的方式輸出,鞏固知識,也便於之後複習
部落格思路
mdn上搜尋Promise,瞭解類和api的定義:
- 定義了哪些屬性,分別代表什麼含義
- api需要傳什麼引數,返回什麼值,可能丟擲什麼異常
- 看官方給出的用例,猜想內部可能的實現
- 編寫原始碼,用官方用例驗證檢視返回值是否一致
API的特性與手寫原始碼
建構函式
- promise有狀態pending、rejected和resolved,所以應該有個變數來儲存狀態
- 建構函式引數excutor是個同步執行的回撥函式,函式執行的引數是兩個函式resolved和rejected,所以promise內部需要定義兩個函式,並且在構造行數中執行excutor的地方傳入
- .then中會傳入回撥函式onResolved和onRejected,在resolved和rejected內會分別會觸發對應的回撥函式,所以需要兩個陣列儲存then中傳進來的回撥
- resolved和rejected只能執行一次,執行後promise的狀態會改變,且引數會傳遞給回撥函式
- onRejected和onResolved非同步執行
- excutor執行拋異常會直接執行rejected,所以excutor的執行需要catch錯誤
const PENDING = "PENDING";
const RESOLVED = "resolved";
const REJECTED = "rejected";
function MyPromise(excutor){
// promise內部儲存著狀態值
this.status = PENDING;
this.data = null;
// then方法傳進來的回撥函式此儲存
this.onResolvedList = [];
this.onRejectedList = [];
let resolved = (value) => {
// resolved函式只能執行一次,所以先判斷狀態是不是pending
if(this.status !== PENDING){
return;
}
// 變更狀態為resolved
this.status = RESOLVED;
// 資料為傳進來的值
this.data = value;
// 判斷是否已經有onResolved回撥已經穿入,有則非同步執行
if(this.onResolvedList.length > 0){
setTimeout(() => {
this.onResolvedList.forEach(onResolved => {
onResolved(value);
});
}, 0);
}
}
let rejected = (reason) => {
if(this.status !== PENDING){
return
}
this.status = REJECTED;
this.data = reason;
if(this.onRejectedList.length > 0){
setTimeout(() => {
this.onRejectedList.forEach(onRejected => {
onRejected(reason);
});
});
}
}
try{
// 執行器函式同步執行,且引數為promise內定義的resolve和rejected
excutor(resolved, rejected);
}catch(error){
// 如果執行器函式出錯直接執行rejected
this.rejected(error);
}
}
then
- then會接受兩個回撥函式onResolved和onRejected
- onResolved和onRejected會非同步呼叫
- then會返回一個新的promise物件
- then的引數如果沒傳,那麼value和reason繼續向下傳遞
- 如果執行then的時候,promise的狀態還是pending,那麼只儲存回撥,並且確保回撥執行後能修改新的promise的狀態
- 如果觸發的對應的回撥函式執行拋異常,那麼返回的新的回撥函式狀態為rejected,reason則會catch到的error
- 如果觸發的對應回撥函式執行返回值不是promise物件,那麼返回新的promise狀態為resolved,value則為傳入then的回撥的返回值
- 如果觸發的對應回撥返回值是promise物件,那麼新的promise返回值的狀態取決於改回撥返回的promise
MyPromise.prototype.then = function(onResolved, onRejected){
// 如果沒有傳onResolved,則設定onResolved為返回value的函式
onResolved = typeof onResolved === "function" ? onResolved : value => value
// 如果沒有傳onRejected,則設定onRejected為拋處reason的函式
onRejected = typeof onRejected === "function" ? onRejected : reason => {throw reason}
return new MyPromise((resolved, rejected) => {
// 傳入要執行的回撥函式
let callBackExcu = (callback) => {
try{
let result = callback(this.data);
if(result instanceof MyPromise){
// 如果回撥返回值還是promise則then返回的promise的狀態取決於回撥的返回的promise,成功就執行resolved失敗就執行rejected
result.then(resolved, rejected);
}else{
// 如果回撥的返回值不為promise則新的promise狀態為resolved
resolved(result)
}
}catch(error){
// 如果回撥執行拋處異常,則新的promise狀態為rejected
rejected(error);
}
}
if(this.status === PENDING){
// 如果狀態為pending則儲存回撥且確保回撥執行後能修改當前返回的promise的狀態
this.onResolvedList.push((value) => {
callBackExcu(onResolved)
});
this.onRejectedList.push((reason) => {
callBackExcu(onRejected)
});
}else{
// 如果狀態不為pending則根據狀態執行對應的回撥,且修改當前promise的狀態
switch(this.status){
case REJECTED:
// onRejected非同步執行
setTimeout(() => {
callBackExcu(onRejected);
});
break;
case RESOLVED:
// onResolved非同步執行
setTimeout(() => {
callBackExcu(onResolved);
});
break;
}
}
});
}
catch
catch和then其實差不多,不同點在於傳入的引數只有onRejected,所以
MyPromise.prototype.catch = function(onRejected){
// catch與then的不同點在於傳入的引數不一樣,不需要傳onResolve
return this.then(null, onRejected);
}
Promise.resolved
- resolved會返回一個promise物件
- 如果傳入的引數本就是一個primise物件則直接返回
- 如果是一個包含“then”方法的物件,返回新的promise物件,且狀態取決於then函式的執行,如果then的執行中拋錯,則新的promise狀態為rejected
- then的引數為兩個回撥函式resolved和rejected
- 如果傳入引數value既不是promise的例項,也不是具備then函式的物件,則返回一個新的promise物件且改物件data就為value
MyPromise.resolve = function(value){
if(value instanceof MyPromise){
// 如果傳入的引數本就是一個primise物件則直接返回
return value;
}else if(typeof value.then === "function"){
return new MyPromise((resolved, rejected) => {
try{
// then的引數為兩個回撥函式resolved和rejected
value.then(resolved, rejected);
}catch(error){
// 如果then的執行中拋錯,則新的promise狀態為rejected
rejected(error);
}
});
}else{
// 如果傳入引數value既不是promise的例項
return new MyPromise((resolved, rejected) => {
resolved(value);
});
}
}
Promise.rejected
- 接受引數reason,返回一個狀態為rejected的data為reason的promise例項
MyPromise.reject = function(reason){
return new MyPromise((resolved, rejected) => {
rejected(reason);
});
}
Promise.all
- 接收的引數是需要滿足可迭代協議,否則會拋錯
- 返回值是個promise
- 如果傳入的引數是個空的可迭代的物件,則返回一個狀態為resolved的可promise例項,data為空陣列,
Promise.all([]) // Promise {<resolved>: Array(0)}
Promise.all("") // Promise {<resolved>: Array(0)}
- 如果傳入的引數中沒有promise例項,或者所有的promise已經是resolved狀態,則返回一個promise狀態為pending,且非同步更新為resolved
let p = Promise.all([1,2,3,4,Promise.resolve(5)])
console.log(p); // Promise {<pending>}
- 如果存在promise且狀態還是pending,返回一個promise例項,且等所有promise都resolved後,狀態更新為resolved,data為傳入的順序
接下來看下原始碼
// 先定義一個驗證引數是否滿足可迭代協議的方法
const isIterable = function(object){
return typeof object[Symbol.iterator] === "function"
&& typeof object[Symbol.iterator]() === "object"
&& typeof object[Symbol.iterator]().next === "function"
}
MyPromise.all = function(iterable){
if(!isIterable(iterable)){
// 不滿足可迭代協議拋錯
throw new TypeError("Object is not iterable");
}
let data = [];
let count = 0;
// 迭代引數生成陣列
let params = Array.from(iterable);
return new MyPromise((resolved, rejected) => {
if(params.length === 0){
// 如果是空的可迭代物件,返回空陣列
resolved(data);
}else{
params.forEach((element, index) => {
// 遍歷每個引數,統一處理成promise的例項
// 這樣就少了一個邏輯分支
let itemPromise = MyPromise.resolve(element);
itemPromise.then(
value => {
// data中的結果需要和傳入引數的順序一致
data[index] = value;
if(count === params.length - 1){
// 說明全都resolved了
resolved(data);
}
count++;
},
reason => {
// reject直接返回
rejected(reason);
}
);
});
}
});
}
Promise.race
- 接收一個可迭代物件,這點與方法"all"相同
- 返回一個新的promise
- 返回的promise狀態為pending,非同步更新為resolved
let p = Promise.race([1,2,3,4]);
console.log(p); // Promise {<pending>}
p.then(
value => {
console.log(value); // Promise{<resolved>: 1}
}
);
- 傳入的若干promise中,只要有一個promise最先resolved或者rejected,則返回的promise狀態更新為resolved
let p1 = new Promise((resolved, rejected) => {
setTimeout(() => {
resolved("p1");
}, 10);
});
let p2 = new Promise((resolved, rejected) => {
setTimeout(() => {
resolved("p2");
}, 100);
});
let p = Promise.race([p2, p1])
p.then(
value => {
console.log(value); // p1
}
);
最後來看一下自己原始碼的實現
MyPromise.race = function(iterable){
if(!isIterable(iterable)){
// 不滿足可迭代協議拋錯
throw new TypeError("Object is not iterable");
}
const params = Array.from(iterable);
return new MyPromise((resolved, rejected) => {
params.forEach((element, index) => {
const itemPromise = MyPromise.resolve(element);
itemPromise.then(
value => {
// 只要有一個promise resolved直接返回
resolved(value);
},
error => {
// 只要有一個promise rejected直接返回
rejected(error);
}
);
});
});
}