手寫Promise原始碼實現
技術標籤:javascript
我們根據Promise的使用方式,實現一個自己的Promise,以此來了解Promise的原始碼思想。
下面的程式碼是一個簡單的Promise的使用案例,我們基於這個案例來實現一個自己的Promise。
首先建立一個Promise物件,在接收的函式中同時呼叫失敗和成功,然後再then方法中列印傳入的引數。
let p = new Promise(funcion(resolve, reject) {
reject('失敗啦');
resolve('成功啦');
});
p.then(function(value) {
console. log(value);
})
這裡需要先定義一個Promise的建構函式。
function Promise() {
}
在建立Promise物件的時候會傳遞一個函式executor,這個函式會立即被呼叫,所以我們在Promise內部立即執行這個函式。
function Promise (executor) {
excutor();
}
executor在執行的時候會傳入兩個方法,一個是resolve,一個reject,所以我們要建立這兩個函式,而且需要把這兩個函式傳遞給executor。
function Promise (executor) {
function resolve () {
}
function reject() {
}
excutor(resolve, reject);
}
我們在呼叫resolve和reject的時候會傳入一個資訊,成功的時候傳遞一個成功的值,失敗的時候傳遞一個失敗的原因。
function Promise (executor) {
function resolve(value) {
}
function reject(reason) {
}
excutor(resolve, reject);
}
Promise的物件存在一個then方法,這個then方法裡面會有兩個引數,一個是成功的回撥onFulfilled,另一個是失敗的回撥onRejected,只要我們呼叫了resolve就會執行onFulfilled,呼叫了reject就會執行onRejected。
Primsie.prototype.then = function(onFulfilled, onRejected) {
}
Promise本身是存在狀態的,預設是等待狀態pending,呼叫了resolve會變成成功狀態,呼叫了reject會變為失敗狀態,所以我們需要在Promise內部定義一個狀態表示當前Promise的狀態。
為了保證this不錯亂,我們定義一個self儲存this。當我們呼叫了resolve或reject的時候,需要讓狀態發生改變.需要注意的是Promise的狀態只可改變一次,所以我們要判斷,只有當狀態未發生改變時,才去改變狀態。
function Promise (executor) {
var self = this;
self.status = 'pending';
function resolve(value) {
if (self.status === 'pending') {
self.status = 'resolved';
}
}
function reject(reason) {
if (self.status === 'pending') {
self.status = 'rejected';
}
}
excutor(resolve, reject);
}
接著呼叫成功resolve或失敗reject的時候傳遞的值我們需要儲存下來,這在後面的then中的方法中還需要使用到這個值。
function Promise (executor) {
var self = this;
self.status = 'pending';
self.value;
self.reason;
function resolve(value) {
if (self.status === 'pending') {
self.status = 'resolved';
self.value = value;
}
}
function reject(reason) {
if (self.status === 'pending') {
self.status = 'rejected';
self.reason = reason;
}
}
excutor(resolve, reject);
}
接著我們在then方法中判斷,當狀態成功的時候執行onFulfilled,當狀態失敗的時候執行onRejected。並且傳入相應的值。
Primsie.prototype.then = function(onFulfilled, onRejected) {
var self = this;
if (self.status === 'resolved') {
onFulfilled(self.value);
}
if (self.status === 'rejected') {
onRejected(self.reason);
}
}
至此我們就實現了一個簡單的Promise程式碼。Promise的概念不是憑空出現的,是在Promise A+規範(https://promisesaplus.com/)
中定義的,這個規範是所有人想實現Promise都必須要基於這個規範。
我們自己寫的Promise可能會和別人的Promise用在一起,為了保證相容性,所有人寫的Promise都要符合這個規範。
規範
1.1 “promise”是一個具有then方法的物件或函式,其行為符合此規範。也就是說Promise是一個物件或者函式。
1.2 “thenable”是一個定義then方法的物件或函式,說句人話也就是這個物件必須要擁有then方法。
1.3 “value”是任何合法的JavaScript值(包括undefined、或者promise)。
1.4 promise中的異常需要使用throw語句丟擲。
1.5 當promise失敗的時候需要給出失敗的原因。
狀態
1.1 promise必須要擁有三個狀態: pending, fulfilled 和 rejected。
1.2 當promise的狀態是pending時,他可以變為成功fulfilled或者失敗rejected。
1.3 如果promise是成功狀態,則他不能轉換成任何狀態,而且需要一個成功的值,並且這個值不能被改變。
1.4 如果promise是失敗狀態,則他不能轉換成任何狀態,而且需要一個失敗的原因,並且這個值不能被改變。
then方法說明
1.1 一個promise必須要有一個then方法,而且可以訪問promise最終的結果,成功或者失敗的值。
1.2 then方法需要接收兩個引數,onFulfilled 和 onRejected這兩個引數是可選引數。
以上我們的程式碼是符合這些規範的,我們繼續向下寫。
現在我們的promise只是實現了第一版最簡易的功能,但是他存在一個非同步邏輯的問題,當我們在Promise函式中非同步呼叫resolve的時候,then方法不會執行。因為then方法執行的時候resolve並沒有執行,也就是promise的狀態還未變化。等到非同步的Promise狀態發生改變的時候,我們的then已經執行完了。而官方的Promise無論then方法是否執行完畢,只要Promise狀態變了,then中繫結的函式就會執行。所以接下來我們要解決這個問題。
下面的例子是官方Promise的應用,同一個例項繫結多個then方法,所有的then繫結的成功或失敗都會相應的執行。
let p = new Promise(funcion(resolve, reject) {
setTimeout(function() {
resolve('成功啦');
}, 1000)
});
p.then(function(value) {
console.log(value);
})
p.then(function(value) {
console.log(value);
})
p.then(function(value) {
console.log(value);
})
這裡我們就需要改造我們的Promise程式碼。當我們呼叫then方法的時候可能還是pending狀態,這個時候我們應該把onFulfilled和onRejected先存起來,當執行了resolve或者reject的時候再執行onFulfilled或onRejected。
所以我們需要定義兩個變數,分別儲存onFulfilled和onRejected。
function Promise (executor) {
var self = this;
self.status = 'pending';
self.value;
self.reason;
self.onResolvedCallbacks = []; // 存放所有成功的回撥。
self.onRejectedCallbacks = []; // 存放所有失敗的回撥。
function resolve(value) {
if (self.status === 'pending') {
self.status = 'resolved';
self.value = value;
}
}
function reject(reason) {
if (self.status === 'pending') {
self.status = 'rejected';
self.reason = reason;
}
}
excutor(resolve, reject);
}
Primsie.prototype.then = function(onFulfilled, onRejected) {
var self = this;
if (self.status === 'resolved') {
onFulfilled(self.value);
}
if (self.status === 'rejected') {
onRejected(self.reason);
}
if (self.status === 'pending') {
self.onResolvedCallbacks.push(onFulfilled);
self.onRejectedCallbacks.push(onRejected);
}
}
因為onFulfilled和onRejected在執行的時候需要傳入對應的value值,所我們這裡用一個函式包裹起來,將對應的值也傳入進去。
Primsie.prototype.then = function(onFulfilled, onRejected) {
var self = this;
if (self.status === 'resolved') {
onFulfilled(self.value);
}
if (self.status === 'rejected') {
onRejected(self.reason);
}
if (self.status === 'pending') {
self.onResolvedCallbacks.push(function () {
onFulfilled(self.value);
});
self.onRejectedCallbacks.push(function() {
onRejected(self.reason);
});
}
}
當我們成功或者失敗的時候,執行onFulfilled和onRejected的函式,也就是在resolve函式中和reject函式中分別迴圈執行對應的陣列中的函式。
function resolve(value) {
if (self.status === 'pending') {
self.status = 'resolved';
self.value = value;
self.onResolvedCallbacks.forEach(function (fn) {
fn();
})
}
}
function reject(reason) {
if (self.status === 'pending') {
self.status = 'rejected';
self.reason = reason;
self.onRejectedCallbacks.forEach(function (fn) {
fn();
})
}
}
這個時候當我們非同步執行resolve方法時候,then中繫結的函式就會執行,並且繫結多個then的時候,多個方法都會執行。
鏈式呼叫
Promise最大的優點就是鏈式呼叫,如果一個then方法返回一個普通值,這個值會傳遞給下一次then中,作為成功的結果。如果返回的是一個primise, 則會把promise的執行結果傳遞下去取決於這個promise的成功或失敗。如果返回的是一個報錯就會執行到下一個then的失敗的函式中。
捕獲錯誤的機制是,預設會找距離自己最近的then的失敗方法,如果找不到就向下繼續找,一直找到catch方法。
let p = new Promise(funcion(resolve, reject) {
setTimeout(function() {
resolve('成功啦');
}, 1000)
});
p.then(function(value) {
retrun 123;
}).then(function(value) {
console.log(value);
}).catch(function(error) {
console.log(error);
}).then(function(data) {
console.log(data);
})
Promise呼叫then後會返回一個新的Promise,因為Promise的狀態只能改變一次,如果使用同一個Promise的話後面的then就失去了成功失敗的自由性。
所以我們需要在then方法之後再去return一個new Promise, 原本的邏輯放在新建立的Promise內部即可,因為他是立即執行的一個函式。我們這裡定義一個promise2接收新建立的Promise,在函式底部返回出去。
Primsie.prototype.then = function(onFulfilled, onRejected) {
var self = this;
const promise2 = new Promise(function (resolve, reject) {
if (self.status === 'resolved') {
onFulfilled(self.value);
}
if (self.status === 'rejected') {
onRejected(self.reason);
}
if (self.status === 'pending') {
self.onResolvedCallbacks.push(function () {
onFulfilled(self.value);
});
self.onRejectedCallbacks.push(function() {
onRejected(self.reason);
});
}
})
return promise2;
}
我們需要拿到當前then方法執行成功或失敗的結果,前一個then方法的返回值會傳遞給下一個then方法,所以這裡我們要關心onFulfilled(self.value) 和 onRejected(self.reason)的返回值,我們這裡定義一個x來接收一下。
...
const x = onFulfilled(self.value);
...
const x = onRejected(self.reason);
...
如果x是一個普通值的話,我們可以直接呼叫promise2的resolve方法,將這個值傳遞出去即可,這樣下一個then就可以獲取的到,所以我們執行resolve(x)即可。
Primsie.prototype.then = function(onFulfilled, onRejected) {
var self = this;
const promise2 = new Promise(function (resolve, reject) {
if (self.status === 'resolved') {
const x = onFulfilled(self.value);
resolve(x);
}
if (self.status === 'rejected') {
const x = onRejected(self.reason);
resolve(x);
}
if (self.status === 'pending') {
self.onResolvedCallbacks.push(function () {
const x = onFulfilled(self.value);
resolve(x);
});
self.onRejectedCallbacks.push(function() {
const x = onRejected(self.reason);
resolve(x);
});
}
})
return promise2;
}
如果失敗拋錯需要執行reject方法,這裡使用try…catch捕獲一下錯誤。也就是判斷then函式的執行結果和promise2的關係。
Primsie.prototype.then = function(onFulfilled, onRejected) {
var self = this;
const promise2 = new Promise(function (resolve, reject) {
if (self.status === 'resolved') {
try {
const x = onFulfilled(self.value);
resolve(x);
} catch(e) {
reject(e);
}
}
if (self.status === 'rejected') {
try {
const x = onRejected(self.reason);
resolve(x);
} catch(e) {
reject(e);
}
}
if (self.status === 'pending') {
self.onResolvedCallbacks.push(function () {
try {
const x = onFulfilled(self.value);
resolve(x);
} catch(e) {
reject(e);
}
});
self.onRejectedCallbacks.push(function() {
try {
const x = onRejected(self.reason);
resolve(x);
} catch(e) {
reject(e);
}
});
}
})
return promise2;
}
我們知道onFulfilled(self.value)返回的值不一定是一個常量,還可能是個promise,我們這裡寫一個方法來判斷下,如果返回值是promise就呼叫promise,如果不是promise才繼續向resolve傳遞。
我們這裡定義一個resolvePromise方法,來解析promise,在這個函式中我們要判斷返回值x和promse2的關係,所以我們需要傳遞promise2引數,x引數,resolve引數和reject引數。
promise2引數,x引數,resolve引數和reject引數不能直接傳遞至resolvePromise中,因為文件要求他們不能在當前的上下文中執行,所以我們要在整個程式碼塊外層新增setTimeout, 在非同步執行緒中新增。
function resolvePromise (promise2, x, resolve, reject) {
}
Primsie.prototype.then = function(onFulfilled, onRejected) {
var self = this;
const promise2 = new Promise(function (resolve, reject) {
if (self.status === 'resolved') {
setTimeout(function() {
try {
const x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e);
}
}, 0)
}
if (self.status === 'rejected') {
setTimeout(function() {
try {
const x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e);
}
}, 0)
}
if (self.status === 'pending') {
self.onResolvedCallbacks.push(function () {
setTimeout(function() {
try {
const x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e);
}
}, 0)
});
self.onRejectedCallbacks.push(function() {
setTimeout(function() {
try {
const x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e);
}
}, 0)
});
}
})
return promise2;
}
resolvePromise這個函式的作用就是判斷x是不是promise,如果是promise就執行他,然後將它的結果呼叫resolve方法,如果x是常量,直接呼叫resolve方法。這些內容在文件上都可以找得到,具體可以自行翻閱文件,這裡就不列出了,直接程式碼實現。
如果promise2和x引用了一個相同的物件,也就是他們是同一個promise物件。
const p = new Promise(function(resolve, reject) {
resolve('成功');
})
const promise2 = p.then(data => { // 這個時候x和promise2就是相等的,也就是自己等待自己去做做完什麼事,等 和 做某事不能同時執行。
return promise2;
})
應該丟擲一個型別錯誤作為錯誤原因,resolvePromise中的程式碼實現如下:
function resolvePromise (promise2, x, resolve, reject) {
if (promise2 === x) { // 防止自己等待自己
return reject(new TypeError('迴圈引用了'));
}
}
如果x是物件或者是一個函式,我們就取他的then方法,但是獲取then方法的時候如果出現異常,就執行失敗。因為then方法可能是物件的一個不可訪問的方法,get的時候報異常,所以我們需要使用try…catch去獲取。
如果x不是一個promise,是個普通值,直接呼叫resolve就可以。
function resolvePromise (promise2, x, resolve, reject) {
if (promise2 === x) { // 防止自己等待自己
return reject(new TypeError('迴圈引用了'));
}
// x是object或者是個function
if ((x !== null && typeof x === 'object') || typeof x === 'function') {
try {
let then = x.then;
} catch (e) {
reject(e);
}
} else {
resolve(x);
}
}
如果then是一個函式,就認為他是Promise, 需要使用call執行then方法,改變this的指向為x, then中傳入成功和失敗的函式, 官方文件中指明成功函式的引數叫y,失敗的引數為r。
如果then不是一個Promise那麼當前這個then是一個普通物件,呼叫resolve方法直接返回即可。
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, function (y) {
resolve(y); // 成功的結果,讓promise2變為成功狀態
}, function (r) {
reject(r);
});
} else {
resolve(x)
}
} catch (e) {
reject(e);
}
這裡面的y有可能也是一個Promise,所以我們這裡不能直接寫resolve(y),應該遞迴判斷y和promise2的關係。所以我們這裡要呼叫resolvePromise。y是then的返回值,和之前的x基本一個概念。
為什麼這裡要用遞迴呢,因為then返回的可能是一個Promise巢狀,也就是Promise中仍舊包含Promise,在Promise的標準這,這樣的寫法是被允許的。所以要用遞迴來解決,拿到最終的返回,也就是基本型別。
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, function (y) {
resolvePromise (promise2, y, resolve, reject)
// resolve(y); // 成功的結果,讓promise2變為成功狀態
}, function (r) {
reject(r);
});
} else {
resolve(x)
}
} catch (e) {
reject(e);
}
我們的Promise可能會和別人的Promise巢狀使用,官方文件要求,Promise中要書寫判斷,避免對方Promise不規範產生的影響。
比如對方的Promise成功和失敗都呼叫了,或者多次呼叫了成功。需要使用一個called的變數來表示Promise有沒有被呼叫過。
一旦狀態改變就不能再改變了。
function resolvePromise (promise2, x, resolve, reject) {
if (promise2 === x) { // 防止自己等待自己
return reject(new TypeError('迴圈引用了'));
}
let called; // 表示Promise有沒有被呼叫過
// x是object或者是個function
if ((x !== null && typeof x === 'object') || typeof x === 'function') {
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, function (y) {
if (called) { // 是否呼叫過
return;
}
called = true;
resolvePromise (promise2, y, resolve, reject)
}, function (r) {
if (called) { // 是否呼叫過
return;
}
called = true;
reject(r);
});
} else { // 當前then是一個普通物件。
resolve(x)
}
} catch (e) {
if (called) { // 是否呼叫過
return;
}
called = true;
reject(e);
}
} else {
if (called) { // 是否呼叫過
return;
}
called = true;
resolve(x);
}
}
我們的Promise還存在一個小問題,如果我們的Promise有多個then方法,只在最後一個then方法中傳遞了onFulfilled, 是需要將Promise的返回值傳遞過去的,也就是下面的程式碼需要用內容輸出。這叫值的穿透。
p.then().then().then(function(data) {
console.log(data);
})
實現起來也比較簡單,假如使用者沒有傳遞onFulfilled,或者傳入的不是函式,我們可以給個預設值,也就是這個引數是一個可選引數。
onRejected的話邏輯也是一樣,只是最後需要將錯誤丟擲,傳遞給下一個reject,如果返回的話會流入下一個resolve。
Primsie.prototype.then = function(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (data) { return data;};
onRejected = typeof onRejected === 'function' ? onRejected : function (err) { throw err;};
}
最後我們還存在一個小問題,我們在呼叫executor的時候,可能也會出錯,只要Promise出現錯誤,就需要走到then的reject中,所以我們需要try…catch一下executor。
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
至此,Promise我們就寫完了,全部程式碼如下:
function Promise (executor) {
var self = this;
self.status = 'pending';
self.value;
self.reason;
self.onResolvedCallbacks = []; // 存放所有成功的回撥。
self.onRejectedCallbacks = []; // 存放所有失敗的回撥。
function resolve(value) {
if (self.status === 'pending') {
self.status = 'resolved';
self.value = value;
self.onResolvedCallbacks.forEach(function (fn) {
fn();
})
}
}
function reject(reason) {
if (self.status === 'pending') {
self.status = 'rejected';
self.reason = reason;
self.onRejectedCallbacks.forEach(function (fn) {
fn();
})
}
}
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
function resolvePromise (promise2, x, resolve, reject) {
if (promise2 === x) { // 防止自己等待自己
return reject(new TypeError('迴圈引用了'));
}
let called; // 表示Promise有沒有被呼叫過
// x是object或者是個function
if ((x !== null && typeof x === 'object') || typeof x === 'function') {
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, function (y) {
if (called) { // 是否呼叫過
return;
}
called = true;
resolvePromise (promise2, y, resolve, reject)
}, function (r) {
if (called) { // 是否呼叫過
return;
}
called = true;
reject(r);
});
} else { // 當前then是一個普通物件。
resolve(x)
}
} catch (e) {
if (called) { // 是否呼叫過
return;
}
called = true;
reject(e);
}
} else {
if (called) { // 是否呼叫過
return;
}
called = true;
resolve(x);
}
}
Promise.prototype.then = function(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (data) { return data;};
onRejected = typeof onRejected === 'function' ? onRejected : function (err) { throw err;};
var self = this;
const promise2 = new Promise(function (resolve, reject) {
if (self.status === 'resolved') {
setTimeout(function() {
try {
const x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e);
}
}, 0)
}
if (self.status === 'rejected') {
setTimeout(function() {
try {
const x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e);
}
}, 0)
}
if (self.status === 'pending') {
self.onResolvedCallbacks.push(function () {
setTimeout(function() {
try {
const x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e);
}
}, 0)
});
self.onRejectedCallbacks.push(function() {
setTimeout(function() {
try {
const x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e);
}
}, 0)
});
}
})
return promise2;
}
測試
可以使用promises-aplus-tests測試自己書寫的Promise是否符合規範。
測試的時候需要提供一段指令碼,通過入口進行測試。
Promise.defer = Promise.deferred = function() {
let dfd = {};
dft.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}
我們首先安裝promises-aplus-tests包。
npm install promises-aplus-tests -g
安裝成功執行測試指令碼
promises-aplus-tests promise.js
Promise靜態方法實現
Promise.all = function (values) {
return new Promise(function (resolve, reject) {
var arr = []; // 最終結果的陣列
var index = 0;
function processData (key, value) {
index++;
arr[key] = value;
if (index === values.length) {
resolve(arr);
}
}
for (var i = 0; i < values.length; i++) {
var current = values[i];
if (current && current.then && typeof current.then === 'function') {
current.then(function(y) {
processData(i, y);
}, reject);
} else {
processData(i, current);
}
}
});
}
Promise.race = function (values) {
return new Promise(function (resolve, reject) {
for (var i = 0; i < values.length; i++) {
var current = values[i];
if (current && current.then && typeof current.then === 'function') {
current.then(resolve, reject);
} else {
resolve(current);
}
}
});
}
Promise.resolve = function(value){
return new Promise((resolve,reject)=>{
resolve(value);
});
}
Promise.reject = function(reason){
return new Promise((resolve,reject)=>{
reject(reason);
});
}