JS手搓Promise的常見方法總結
1. 手搓 Promise
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
function MyPromise(fn) {
// 保存初始化狀態(tài)
var self = this;
// 初始化狀態(tài)
this.state = PENDING;
// 用于保存 resolve 或者 rejected 傳入的值
this.value = null;
// 用于保存 resolve 的回調函數
this.resolvedCallbacks = [];
// 用于保存 reject 的回調函數
this.rejectedCallbacks = [];
// 狀態(tài)轉變?yōu)?resolved 方法
function resolve(value) {
// 判斷傳入元素是否為 Promise 值,如果是,則狀態(tài)改變必須等待前一個狀態(tài)改變后再進行改變
if (value instanceof MyPromise) {
return value.then(resolve, reject);
}
// 保證代碼的執(zhí)行順序為本輪事件循環(huán)的末尾
setTimeout(() => {
// 只有狀態(tài)為 pending 時才能轉變,
if (self.state === PENDING) {
// 修改狀態(tài)
self.state = RESOLVED;
// 設置傳入的值
self.value = value;
// 執(zhí)行回調函數
self.resolvedCallbacks.forEach(callback => {
callback(value);
});
}
}, 0);
}
// 狀態(tài)轉變?yōu)?rejected 方法
function reject(value) {
// 保證代碼的執(zhí)行順序為本輪事件循環(huán)的末尾
setTimeout(() => {
// 只有狀態(tài)為 pending 時才能轉變
if (self.state === PENDING) {
// 修改狀態(tài)
self.state = REJECTED;
// 設置傳入的值
self.value = value;
// 執(zhí)行回調函數
self.rejectedCallbacks.forEach(callback => {
callback(value);
});
}
}, 0);
}
// 將兩個方法傳入函數執(zhí)行
try {
fn(resolve, reject);
} catch (e) {
// 遇到錯誤時,捕獲錯誤,執(zhí)行 reject 函數
reject(e);
}
}
MyPromise.prototype.then = function(onResolved, onRejected) {
// 首先判斷兩個參數是否為函數類型,因為這兩個參數是可選參數
onResolved =
typeof onResolved === "function"
? onResolved
: function(value) {
return value;
};
onRejected =
typeof onRejected === "function"
? onRejected
: function(error) {
throw error;
};
// 如果是等待狀態(tài),則將函數加入對應列表中
if (this.state === PENDING) {
this.resolvedCallbacks.push(onResolved);
this.rejectedCallbacks.push(onRejected);
}
// 如果狀態(tài)已經凝固,則直接執(zhí)行對應狀態(tài)的函數
if (this.state === RESOLVED) {
onResolved(this.value);
}
if (this.state === REJECTED) {
onRejected(this.value);
}
};2. 手搓 Promise.then
then 方法返回一個新的 promise 實例,為了在 promise 狀態(tài)發(fā)生變化時(resolve / reject 被調用時)再執(zhí)行 then 里的函數,我們使用一個 callbacks 數組先把傳給then的函數暫存起來,等狀態(tài)改變時再調用。
那么,怎么保證后一個 **then** 里的方法在前一個 **then**(可能是異步)結束之后再執(zhí)行呢?
我們可以將傳給 then 的函數和新 promise 的 resolve 一起 push 到前一個 promise 的 callbacks 數組中,達到承前啟后的效果:
- 承前:當前一個
promise完成后,調用其resolve變更狀態(tài),在這個resolve里會依次調用callbacks里的回調,這樣就執(zhí)行了then里的方法了 - 啟后:上一步中,當
then里的方法執(zhí)行完成后,返回一個結果,如果這個結果是個簡單的值,就直接調用新promise的resolve,讓其狀態(tài)變更,這又會依次調用新promise的callbacks數組里的方法,循環(huán)往復。。如果返回的結果是個promise,則需要等它完成之后再觸發(fā)新promise的resolve,所以可以在其結果的then里調用新promise的resolve
then(onFulfilled, onReject){
// 保存前一個promise的this
const self = this;
return new MyPromise((resolve, reject) => {
// 封裝前一個promise成功時執(zhí)行的函數
let fulfilled = () => {
try{
const result = onFulfilled(self.value); // 承前
return result instanceof MyPromise? result.then(resolve, reject) : resolve(result); //啟后
}catch(err){
reject(err)
}
}
// 封裝前一個promise失敗時執(zhí)行的函數
let rejected = () => {
try{
const result = onReject(self.reason);
return result instanceof MyPromise? result.then(resolve, reject) : reject(result);
}catch(err){
reject(err)
}
}
switch(self.status){
case PENDING:
self.onFulfilledCallbacks.push(fulfilled);
self.onRejectedCallbacks.push(rejected);
break;
case FULFILLED:
fulfilled();
break;
case REJECT:
rejected();
break;
}
})
}注意:
- 連續(xù)多個
then里的回調方法是同步注冊的,但注冊到了不同的callbacks數組中,因為每次then都返回新的promise實例(參考上面的例子和圖) - 注冊完成后開始執(zhí)行構造函數中的異步事件,異步完成之后依次調用
callbacks數組中提前注冊的回調
3. 手搓 Promise.all
1) 核心思路
- 接收一個 Promise 實例的數組或具有 Iterator 接口的對象作為參數
- 這個方法返回一個新的 promise 對象,
- 遍歷傳入的參數,用Promise.resolve()將參數"包一層",使其變成一個promise對象
- 參數所有回調成功才是成功,返回值數組與參數順序一致
- 參數數組其中一個失敗,則觸發(fā)失敗狀態(tài),第一個觸發(fā)失敗的 Promise 錯誤信息作為 Promise.all 的錯誤信息。
2)實現代碼
一般來說,Promise.all 用來處理多個并發(fā)請求,也是為了頁面數據構造的方便,將一個頁面所用到的在不同接口的數據一起請求過來,不過,如果其中一個接口失敗了,多個請求也就失敗了,頁面可能啥也出不來,這就看當前頁面的耦合程度了
function promiseAll(promises) {
return new Promise(function(resolve, reject) {
if(!Array.isArray(promises)){
throw new TypeError(`argument must be a array`)
}
var resolvedCounter = 0;
var promiseNum = promises.length;
var resolvedResult = [];
for (let i = 0; i < promiseNum; i++) {
Promise.resolve(promises[i]).then(value=>{
resolvedCounter++;
resolvedResult[i] = value;
if (resolvedCounter == promiseNum) {
return resolve(resolvedResult)
}
},error=>{
return reject(error)
})
}
})
}
// test
let p1 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(1)
}, 1000)
})
let p2 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(2)
}, 2000)
})
let p3 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(3)
}, 3000)
})
promiseAll([p3, p1, p2]).then(res => {
console.log(res) // [3, 1, 2]
})4. 手搓 Promise.race
該方法的參數是 Promise 實例數組, 然后其 then 注冊的回調方法是數組中的某一個 Promise 的狀態(tài)變?yōu)?fulfilled 的時候就執(zhí)行. 因為 Promise 的狀態(tài)只能改變一次, 那么我們只需要把 Promise.race 中產生的 Promise 對象的 resolve 方法, 注入到數組中的每一個 Promise 實例中的回調函數中即可.
Promise.race = function (args) {
return new Promise((resolve, reject) => {
for (let i = 0, len = args.length; i < len; i++) {
args[i].then(resolve, reject)
}
})
}到此這篇關于JS手搓Promise的常見方法總結的文章就介紹到這了,更多相關JS Promise內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家
相關文章
uploadify在Firefox下丟失session問題的解決方法
在用uploadify上傳插件時遇到了一個問題,在讀session時認為沒有權限而被攔截了,后來在后臺打印登錄時產生session的id和上傳時讀取session的id,解決方法如下,感興趣的朋友可以了解下2013-08-08

