Promise 簡單實現
前言
你可能知道,javascript 的任務執行的模式有兩種:同步和異步。
異步模式非常重要,在瀏覽器端,耗時很長的操作(例如 ajax 請求)都應該異步執行,避免瀏覽器失去響應。
在異步模式編程中,我們經常使用回調函數。一不小心就可能寫出以下這樣的代碼:
//事件1
doSomeThing1(function() {
//事件2
doSomeThing2(function() {
//事件3
doSomeThing3();
});
});
當你的需要異步執行的函數越來越多,你的層級也會越來越深。
這樣的寫法存在的缺點是:
- 不利於閱讀
- 各個任務之間的高度耦合,難以維護
- 對異常的處理比較難
用 Promise 可以避免這種回調地獄,可以寫成這樣
//事件1
doSomeThing1()
.then(function() {
//事件2
return doSomeThing2();
})
.then(function() {
//事件3
return doSomeThing3();
})
.catch(function() {
//這裏可以很方便的做異常處理
});
在市面上有許多庫都實現了 Promise,例如:Q.js 、when.js ,es6 也將 Promise 納入了標準中
es6 的 Promise 使用方法可以參考阮一峯的 http://es6.ruanyifeng.com/#do... ,我就不在做具體介紹
接下來,我們模仿 ES6 的 promise,一步一步來實現一個簡單版的 Promise。
構造函數
我們使用 Promise 的時候,
const promise = new Promise((resolve, reject)=>{
// ... some code
if (/* 異步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
Promise 是一個構造函數,接收一個函數,函數裏有兩個參數,resolve、reject。
我們可以這樣子實現:
class PromiseA {
constructor(executor) {
const resolve = value => {
this.resolve(value);
};
const reject = err => {
this.reject(err);
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
resolve(value) {
this.resultValue = value;
}
reject(error) {
this.resultValue = error;
}
}
then 方法
promise 中,用的最頻繁的就是 then 方法,then 方法有兩個參數,一個是 promise 成功時候的回調,一個是失敗的回調。
實現方式爲:
class PromiseA {
constructor(executor) {
const resolve = value => {
this.resolve(value);
};
const reject = err => {
this.reject(err);
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
resolve(value) {
this.resultValue = value;
if (this.fullfillCallback) {
//++++++++
this.fullfillCallback(value);
}
}
reject(error) {
this.resultValue = error;
if (this.rejectCallback) {
//++++++++
this.rejectCallback(value);
}
}
then(resolve, reject) {
this.fullfillCallback = resolve;
this.rejectCallback = resolve;
}
}
then 方法有以下幾個特性:
- 支持鏈式操作
- 每次 then 方法都是返回新的 Promise
- 當前 promise 的狀態通過返回值傳遞給下一個 promise
- 錯誤冒泡,即如果當前 promise 沒有提供 onReject 方法,會把錯誤冒泡到下一個 promise,方便處理
then(onResolve,onReject){
//返回新的Promise並支持鏈式操作
return new PromiseA((resolve,reject)=>{
this.fullfillCallback = (value)=>{
try {
if (onResolve) {
let newValue = onResolve(value);
resolve(newValue); //將當前promise執行結果,傳遞給下一個promise
} else {
resolve(value);
}
} catch (err) {
reject(err);
}
}
//類似fullfillCallback
this.rejectCallback = (value)=>{
try {
if (onReject) {
let newValue = onReject(value);
resolve(newValue);
} else {
//錯誤冒泡
reject(value);
}
} catch (err) {
reject(err);
}
}
});
}
這樣我們就實現了一個簡單版的 then 方法了
加強版 then
上面的實現,模擬了 then 方法的邏輯,但是還有一些缺點:
- then 方法只能添加一個,例如
let p = new PromiseA(resolve => {
setTimeout(() => {
resolve(1);
}, 0);
});
p.then(value => {
console.log('then-->' + value);
}); //無輸出,因爲沒觸發到,被後一個覆蓋了
p.then(value => {
console.log('then2-->' + value);
}); ////then---> 1
- promise 沒有狀態,當 promsie 在添加 then 的時候已經完成了,沒法得到結果
- 沒有實現:如果上一個 promise 的返回值也是一個 Promise 對象時,則會等到這個 Promise resolve 的時候才執行下一個
爲了解決第一點,引入了事件監聽,簡單的實現如下:
export default class EventEmitter {
constructor() {
this._events = {};
}
on(type, fn, context = this) {
if (!this._events[type]) {
this._events[type] = [];
}
this._events[type].push([fn, context]);
}
trigger(type) {
let events = this._events[type];
if (!events) {
return;
}
let len = events.length;
let eventsCopy = [...events];
for (let i = 0; i < len; i++) {
let event = eventsCopy[i];
let [fn, context] = event;
if (fn) {
fn.apply(context, [].slice.call(arguments, 1));
}
}
}
}
所以進一步對 PromiseA 進行改造:
const STATUS = {
PENDING: 'pending',
FULFILLED: 'fulfilled',
REJECTED: 'rejected'
};
const EventType = {
fulfill: 'fulfill',
reject: 'reject'
};
class PromiseA {
constructor(executor) {
//初始化事件監聽及狀態
this.eventEmitter = new EventEmitter();
this.status = STATUS.PENDING;
const resolve = value => {
if (value instanceof PromiseA) {
value.then(
value => {
this.resolve(value);
},
error => {
this.reject(error);
}
);
} else {
this.resolve(value);
}
};
const reject = err => {
this.reject(err);
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
resolve(value) {
//增加狀態
if (this.status === STATUS.PENDING) {
this.status = STATUS.FULFILLED;
this.resultValue = value;
this.eventEmitter.trigger(EventType.fulfill, value);
}
}
reject(error) {
//增加狀態
if (this.status === STATUS.PENDING) {
this.status = STATUS.REJECTED;
this.resultValue = error;
this.eventEmitter.trigger(EventType.reject, error);
}
}
then(onResolve, onReject) {
//根據狀態不同處理
if (this.status === STATUS.PENDING) {
return new PromiseA((resolve, reject) => {
//增加事件監聽
this.eventEmitter.on('fulfill', value => {
try {
if (onResolve) {
let newValue = onResolve(value);
resolve(newValue);
} else {
resolve(value);
}
} catch (err) {
reject(err);
}
});
//增加事件監聽
this.eventEmitter.on('reject', value => {
try {
if (onReject) {
let newValue = onReject(value);
resolve(newValue);
} else {
reject(value);
}
} catch (err) {
reject(err);
}
});
});
}
if (
this.status === STATUS.FULFILLED ||
this.status === STATUS.REJECTED
) {
return new PromiseA((resolve, reject) => {
let callback = returnValue;
if (this.status === STATUS.FULFILLED) {
callback = onResolve;
}
if (this.status === STATUS.REJECTED) {
callback = onReject;
}
try {
let newValue = callback(this.resultValue);
resolveValue(newValue, resolve, reject);
} catch (err) {
reject(err);
}
});
}
}
}
到這裏,我們的 then 方法基本就完成了。
最後還有一個小知識點,就是執行時機的問題:
setTimeout(function() {
console.log(4);
}, 0);
new Promise(function(resolve) {
console.log(1);
resolve();
}).then(function() {
console.log(3);
});
console.log(2);
//輸出結果會是: 1、2、3、4
promise.then,是異步的,屬於 microtask,執行時機是本次事件循環結束之前,而 setTimeout 是 macrotask,執行時機是在下一次事件循環的開始之時
實現這個功能,我利用了第三方庫 microtask 來模擬。所以 PromiseA 修改爲:
resolve(value) {
microtask(() => {
if (this.status === STATUS.PENDING) {
this.status = STATUS.FULFILLED;
this.resultValue = value;
this.eventEmitter.trigger(EventType.fulfill, value);
}
})
}
reject(error) {
microtask(() => {
if (this.status === STATUS.PENDING) {
this.status = STATUS.REJECTED;
this.resultValue = error;
this.eventEmitter.trigger(EventType.reject, error);
}
});
}
到此爲止,我們的 then 方法已經大功告成了。最困難的一步已經解決了
catch
Promise 跟普通回調的一大優勢就是異常處理,我們推薦使用 Promise 的時候,總是使用 catch 來代替 then 的第二個參數。即是:
//bad
let p = new Promise((resolve, reject) => {
//...異步操作
}).then(
value => {
//成功
},
() => {
//失敗
}
);
//good
let p = new Promise((resolve, reject) => {
//...異步操作
})
.then(value => {
//成功
})
.catch(() => {
//失敗
});
接下來讓我們實現 catch 方法:
catch(reject) {
return this.then(null, reject);
}
哈哈~ , 你沒看錯。你已經實現了 catch 方法
all 方法
Promise.all 是一個很好用的方法。接受一個 promise 數組,然後等到所有的異步操作都完成了,就返回一個數組,包含對應的值
具體實現如下:
static all(promiseList = []) {
//返回promise以便鏈式操作
return new PromiseA((resolve, reject) => {
let results = [];
let len = promiseList.length;
let resolveCount = 0; //用於計數
let resolver = function (index, value) {
resolveCount++;
results[index] = value;
if (resolveCount === len) {
resolve(results);
}
};
//遍歷執行所有的promise
promiseList.forEach((p, i) => {
if (p instanceof PromiseA) {
p.then((value) => {
resolver(i, value);
}, (err) => {
reject(err);
})
} else {
resolver(i, p);
}
})
});
}
race 方法
race 方法爲競速,第一執行完的爲準。所以只需循環一遍執行就可以了。
當有第一個將 Promise 的狀態改變成 fullfilled 或 reject 之後,其他的就都無效了
static race(promiseList = []) {
return new PromiseA((resolve, reject) => {
promiseList.forEach((p, i) => {
if (p instanceof PromiseA) {
p.then((value) => {
resolve(value);
}, (err) => {
reject(err);
})
} else {
resolve(p);
}
})
})
}
小結
我們實現了一個簡單版的 promise, 還有一些其他的方法在這裏沒有講到。感興趣的朋友可以自行去研究哈~
附上代碼完整的實現 : https://github.com/chen434202...