ES6 Promise 的不完全實現
一、Promise/A+ 規範
1.Promise存在三個狀態:pending(等待態)、fulfilled(成功態)、rejected(失敗態);
2.pending為初始態,並可以轉化為fulfilled和rejected;
3.成功時,不可轉為其他狀態,且必須有一個不可改變的值 (value);
4.失敗時,不可轉為其他狀態,且必須有一個不可改變的原因 (reason);
5.new Promise(executor = (resolve, reject) => {resolve(value)}), resolve(value)將狀態置為 fulfilled;
6.new Promise(executor = (resolve, reject) => {reject(reason)}), reject(reason)將狀態置為 rejected;
8.thenable: then(onFulfilled, onRejected?);
8.1 onFulfilled: status 為 fulfilled,執行 onFulfilled, 傳入 value
8.2 onRejected: status 為 rejected, 執行 onRejected
二、同步 Promise
同步 Promise 沒啥需要特別注意的地方,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
// 1.Promise存在三個狀態:pending(等待態)、fulfilled(成功態)、rejected(失敗態)
const STATUS_PENDING = 'pending'
const STATUS_FULFILLED = 'fulfilled'
const STATUS_REJECTED = 'rejected'
class myPromise {
constructor(executor) {
// pending為初始態,並可以轉化為fulfilled和rejected
this .status = STATUS_PENDING
this .value = '' // 3
this .reason = '' // 4
let resolve = value => {
// 5.
if ( this .status === STATUS_PENDING) {
this .status = STATUS_FULFILLED
this .value = value
}
}
let reject = reason => {
//6.
if ( this .status === STATUS_PENDING) {
this .status = STATUS_REJECTED
this .reason = reason
}
}
// 7.
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
// 8.
then(onFulfilled = () => {}, onRejected = () => {}) {
// 8.1
if ( this .status === STATUS_FULFILLED) {
onFulfilled( this .value)
}
// 8.2
if ( this .status === STATUS_REJECTED) {
onRejected( this .reason)
}
}
}
let ps = new myPromise(resolve => {
console.log( 'before resolve' )
resolve(1)
})
ps.then(res => {
console.log(res)
})
let pe = new myPromise((resolve, reject) => {
console.log( 'before reject' )
reject( 'reject error' )
})
pe.then(res => {
console.log(res)
}, error => {
console.log(error)
})
|
在上面的兩個例子中,所有任務的執行都是同步的,可以與接下來的非同步 promise 的執行順序對比下。
三、非同步 Promise
先放下我們的執行例項:
1 2 3 4 5 6 7 8 9 |
let pa = new myPromise(resolve => {
console.log( 'before resolve' )
setTimeout(()=>{
resolve(1)
},1000)
})
pa.then(res => {
console.log(res)
})
|
在這裡,我們的 executor 函式體中有非同步的程式碼塊,那麼在執行executor(resolve, reject);的時候,setTimeout中的任務就會被推入非同步執行棧中,等待主執行緒中的巨集任務(詳見EventLoop in Js)全部計算完成再執行這個任務,因此,我們打斷點會發現, then()的執行會早於 resolve(1), 而在then()執行的時候,pa.status 依然是 pending,接下啦根據邏輯判斷 then 函式執行完成退出,然後執行非同步任務,整個程式碼執行完畢,故控制檯只會打印出 'before resolve'。
明白了這裡的執行順序,我們即可以進行完善,程式碼如下:(標記 改 A)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
const STATUE_PENDING = 'pending' ;
const STATUE_FULFILLED = 'fulfilled' ;
const STATUS_REJECTED = 'rejected' ;
class MyPromise {
constructor(executor) {
// pending 是初始態,並可以轉化成 fulfilled 和 rejected
this .status = STATUE_PENDING;
this .value = '' ; // 3.
this .reason = '' ; // 4.
// 改 A start
// 存放成功的陣列
this .onResolvedCallbacks = [];
// 存放失敗的陣列
this .onRejectedCallbacks = [];
// 改 A end
let resolve = value => {
// 5.
if ( this .status === STATUE_PENDING) {
this .status = STATUE_FULFILLED;
this .value = value;
// 改 A start
// 成功之後的執行棧,
this .onResolvedCallbacks.forEach(fn => fn());
// 改 A end
}
}
let reject = reason => {
// 6.
if ( this .status === STATUE_PENDING) {
this .status = STATUS_REJECTED;
this .reason = reason;
// 改 A start
// 成功之後的執行棧,
this .onRejectedCallbacks.forEach(fn => fn());
// 改 A end
}
}
// 7.
try {
executor(resolve, reject);
} catch (error) {
reject(err);
}
}
// 8
then(onFulfilled = () => {}, onRejected = () => {}){
// 8.1
if ( this .status === STATUE_FULFILLED) onFulfilled( this .value);
// 8.2
if ( this .status === STATUS_REJECTED) onRejected( this .reason);
// 改 A start
// 如果是處於非同步任務的
if ( this .status === STATUE_PENDING) {
// 推入相應的執行棧
this .onResolvedCallbacks.push(() => onFulfilled( this .value));
this .onRejectedCallbacks.push(() => onRejected( this .reason));
}
// 改 A end
}
}
|
為什麼要分析下這邊的執行順序,一來複習下 EventLoop,二來對下面的鏈式呼叫的理解比較重要(這裡也是打斷點才發現的,推翻了一直以來對 Promise.then 的理解,之前一直認為 executor 中的非同步任務執行完了才真正的去執行 then 函式和裡面的onFulfilled/onRejected 函式)。
四、new Promise().then().then()... 鏈式呼叫
Promise 的鏈式呼叫和 jquery 的鏈式呼叫是不同的,在 jquery 或者一些其他的三方包中,我們在函式末尾加上returnthis 即可實現,所以這裡無論鏈多少,都是在同一個物件上做文章。而 Promise 的每一次(鏈式)呼叫,其都會產生一個新的 Promise 物件, 並基於這個新的 Promise 呼叫 then 函式,雖然我們寫的時候是.then().then()...
。 首先我們來看例項:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
let pc = new MyPromise((resolve, reject) => {
console.log(0);
setTimeout(() => {
resolve(1);
}, 3000);
})
pc.then(res => {
console.log(res);
return new MyPromise(resolve => {
console.log(2);
setTimeout(() => {
resolve(3)
}, 3000);
})
}).then(res => {
console.log(res);
})
|
注意下結構,我們在第一個 then 的引數函式中會有一個新的 Promise 返回。
然後是 MyPromise 類:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
const STATUE_PENDING = 'pending' ;
const STATUE_FULFILLED = 'fulfilled' ;
const STATUS_REJECTED = 'rejected' ;
function resolvePromise(promise2, x, resolve, reject) {
// 處理迴圈引用報錯
if (x === promise2) {
// reject 報錯
return reject( new TypeError( 'chaining cycle detected for promise' ));
}
// 記錄, 防止多次呼叫
let called;
// x 是物件(不包括 null)或者函式
if (x != null && ( typeof x === 'object' || typeof x === 'function' )) {
try {
// A+ 規定,宣告 then = x 的 then 方法
let then = x.then;
// then 是 function,則預設是 promise
if ( typeof then === 'function' ) {
// 就讓 then 執行, 第一個引數是 this, 後面是成功的回撥和失敗的回撥
then.call(x, y => {
// 成功和失敗只能呼叫一個
if (called) return ;
called = true ;
// resolve 的結果依舊是promise 那就繼續解析
resolvePromise(promise2, y, resolve, reject);
}, err => {
// 成功和失敗只能呼叫一個
if (called) return ;
called = true ;
// 失敗,停止繼續呼叫
reject(err);
})
} else { // 不是的話直接 resolve 即可
resolve(x);
}
} catch (error) {
// 出錯,即失敗
if (called) return ;
called = true ;
// 取 then 出錯了那就不繼續了
reject(error);
}
} else {
resolve(x);
}
}
class MyPromise {
constructor(executor) {
this .status = STATUE_PENDING;
this .value = '' ;
this .reason = '' ;
this .onResolvedCallbacks = [];
this .onRejectedCallbacks = [];
let resolve = value => {
if ( this .status === STATUE_PENDING) {
this .status = STATUE_FULFILLED;
this .value = value;
this .onResolvedCallbacks.forEach(fn => fn());
}
}
let reject = reason => {
if ( this .status === STATUE_PENDING) {
this .status = STATUS_REJECTED;
this .reason = reason;
this .onRejectedCallbacks.forEach(fn => fn());
}
}
try {
executor(resolve, reject);
} catch (error) {
reject(err);
}
}
then(onFulfilled, onRejected){
// onFulfilled 不是函式, 則忽略,直接返回 value
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
// onRejected 不是函式, 則忽略,直接扔出錯誤
onRejected = typeof onRejected === 'function' ? onRejected : err => {
throw err;
};
let promise2 = new MyPromise((resolve, reject) => {
if ( this .status === STATUE_FULFILLED) {
// 推一個非同步任務
setTimeout(() => {
try {
let x = onFulfilled( this .value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error)
}
}, 0);
}
if ( this .status === STATUS_REJECTED) {
// 推一個非同步任務
setTimeout(() => {
try {
let x = onRejected( this .reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error)
}
}, 0);
}
if ( this .status === STATUE_PENDING) {
// 推到執行棧中
this .onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled( this .value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error)
}
}, 0);
});
this .onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected( this .reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error)
}
}, 0);
})
}
});
return promise2;
}
}
|
這裡要注意的是 then 函式的執行會產生一個新的 Promise, 第一個 then 函式的引數函式的執行也會產生一個新的 Promise。
五、其他:catch、resolve、reject、race和all
這裡除 catch 外其餘均是靜態方法:
1. catch(特殊的 then 方法):
1 2 3 |
catch (fn){
return this .then( null ,fn)
}
|
2.reslove(resolve 一個值)
1 |
MyPromise.resolve = val => new Promise(resolve=> resolve(val))
|
3.reject(reject 一個值)
1 |
MyPromise.reject = val => new Promise((resolve,reject)=> reject(val))
|
4.race Promise.race([p1, p2, p3])裡面哪個結果獲得的快,就返回那個結果,不管結果本身是成功狀態還是失敗狀態。
1 2 3 4 5 |
MyPromise.race = promises => {
return new MyPromise((resolve, reject) =>
promises.forEach(pro => pro.then(resolve, reject))
)
}
|
5.all Promise.all可以將多個Promise例項包裝成一個新的Promise例項。同時,成功和失敗的返回值是不同的,成功的時候返回的是一個結果陣列,而失敗的時候則返回最先被reject失敗狀態的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
MyPromise.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 ;
});
}
}
});
}
|