該實現是按照promiseA+規範來進行梳理的 當使用promise的時候需要先new一個例項,所以我們要構造一個promise建構函式
let p = new Promise(function(resolve, reject){
// 先調誰,就走誰
reject('不對了'); // 失敗
resolve(1); // 成功
// 如果丟擲異常也會走到then的失敗函式裡
throw new Error('錯了');
});
p.then(function(data) {
console.log(data);
}, function(err) {
console.log(err); // '不對了'
});
複製程式碼
new的Promise例項包含一個執行函式(executor),該函式有兩個引數,分別是成功和失敗的方法
這裡要記住一點是成功和失敗的方法是互斥的,同時呼叫時,先呼叫的先執行,後面呼叫的不會再執行
狀態
都知道promise是有三種狀態值的
- pending(待定) 預設的狀態
- fulfilled(成功)
- rejected(失敗)
那麼給建構函式寫一個status,用來儲存狀態的變化 pendding可以轉為fulfilled或者rejected, 一旦轉換了,status就不會改變了,要麼是成功態,要麼是失敗態
// 實現promise
function Promise(executor) {
const self = this;
self.status = 'pending'; // 預設的狀態,pending->fulfilled, pending->rejected
self.value = null; // 成功的值
self.reason = null; // 失敗的原因
function resolve(value) { // 成功
if (self.status === 'pending') {
self.status = 'fulfilled';
self.value = value;
}
}
function reject(reason) { // 失敗
if (self.status === 'pending') {
self.status = 'rejected';
self.reason = reason;
}
}
try {
executor(resolve, reject);
} catch(e) { // 如果丟擲錯誤,就直接走失敗的方法
reject(e);
}
}
Promise.prototype.then = function(onFulfilled, onRejected) {
const self = this;
if (self.status === 'fulfilled') {
onFulfilled();
}
if (self.status === 'rejected') {
onRejected();
}
};
module.exports = Promise;
複製程式碼
上面寫了很多,但是發現都是處理同步的情況,當遇到非同步的時候該怎麼處理呢,我們繼續往下寫
非同步promise
promise中可以寫非同步程式碼,並且可以進行多次then
let p = new Promise(function(resolve, reject){
// 非同步操作
setTimeout(function() {
resolve('ok');
}, 1000);
}).then(function(data) {
console.log(data); // 'ok'
return `下面的人接著 + ${data}`;
}, function(err) {
console.log(err);
}).then(function(data) {
console.log(data); // '下面的人接著 ok'
}, function(err) {
console.log(err);
});
複製程式碼
實現一下非同步的promise
promise例項可以多次then,當成功後會將then中的成功的方法依次執行, 我們可以先將then中成功的回撥和失敗的回撥存到陣列內,當成功時呼叫成功的陣列即可
function Promise() {
self.status = 'pending';
self.value = null;
self.reason = null;
+ self.onFulfilledCb = []; // 存放then成功的回撥
+ self.onRejectedCb = []; // 存放then失敗的回撥
function resolve(value) {
if (self.status === 'pending') {
self.status = 'fulfilled';
self.value = value;
+ self.onFulfilledCb.forEach(function (fn) {
fn();
});
}
}
function reject(reason) {
if (self.status === 'pending') {
self.status = 'rejected';
self.reason = reason;
+ self.onRejectedCb.forEach(function (fn) {
fn();
});
}
}
try {
executor(resolve, reject);
} catch(e) { // 如果丟擲錯誤,就直接走失敗的方法
reject(e);
}
}
Promise.prototype.then = function(onFulfilled, onRejected) {
const self = this;
if (self.status === 'fulfilled') {
onFulfilled(self.value);
}
if (self.status === 'rejected') {
onRejected(self.reason);
}
// 當呼叫then時 可能沒成功也沒失敗,就處於pending狀態
+ if (self.status === 'pending') {
// 將成功的回撥新增到陣列中
self.onFulfilledCb.push(function () {
onFulfilled(self.value);
});
self.onRejectedCb.push(function () {
onRejected(self.reason);
});
}
};
module.exports = Promise;
複製程式碼
鏈式呼叫
上面的程式碼實現了非同步呼叫的時候then方法是怎麼處理的情況,
那麼接下來就來到重要的環節了,大家都知道promise的then方法也是支援鏈式呼叫的
不過then的鏈式呼叫可不同於jquery返回this的方式實現的
☆️ 它是返回了一個新的promise
下面來看程式碼
let p = new Promise(function(resolve, reject) {
resolve('ok');
});
p.then(function (data) {
console.log(data);
return data;
}, function (err) {
console.log(err);
// return '有返回值就走成功態'
}).then(function (data) {
console.log('2 '+data);
}, function (err) {
console.log(err);
})
複製程式碼
now讓我們開始實現鏈式呼叫,還是老樣子,+號代表實現的主要程式碼:
Promise.prototype.then = function (onFulfilled, onRejected) {
const self = this;
+ let promise2; // 定義一個promise2變數
if (self.status === 'fulfilled') {
// 返回一個新的promise
+ promise2 = new Promise(function (resolve, reject) {
// x可能是個普通值,也可能是個promise
+ let x = onFulfilled(self.value);
// x也可能是別人寫的promise,寫一個函式統一去處理
+ resolvePromise(promise2, x, resolve, reject);
});
}
if (self.status === 'rejected') {
+ promise2 = new Promise(function (resolve, reject) {
+ let x = onRejected(self.reason);
+ resolvePromise(promise2, x, resolve, reject);
});
}
// 當呼叫then時 可能沒成功也沒失敗,就處於pending狀態
if (self.status === 'pending') {
// 將成功的回撥新增到陣列中
+ promise2 = new Promise(function (resolve, reject) {
self.onFulfilledCb.push(function () {
+ let x = onFulfilled(self.value);
+ resolvePromise(promise2, x, resolve, reject);
});
self.onRejectedCb.push(function () {
+ let x = onRejected(self.reason);
+ resolvePromise(promise2, x, resolve, reject);
});
});
}
return promise2;
};
function resolvePromise(p2, x, resolve, reject) {
if (p2 === x) { // 不能返回自己
return reject(new TypeError('迴圈引用'));
}
let called; // 表示是否呼叫成功or失敗
// x返回的可能是物件和函式也可能是一個普通的值
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;
// y可能還是個promise,所以遞迴繼續解析直到返回一個普通值
resolvePromise(p2, y, resolve, reject);
}, function (e) {
if (called) return;
called = true;
reject(e);
});
} else {
// 處理then不是函式的情況,如{then: 1},就直接返回成功
resolve(x);
}
} catch(e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x); // 返回一個普通值
}
}
複製程式碼
通過以上程式碼也可以得出了一些結論
- 如果then中無論是成功的回撥還是失敗的回撥有return返回值,都會走下一個then的成功
- 如果第一個promise返回了一個普通值,會進入下一次then的成功回撥;如果第一個promise返回了一個新的promise,需要等待返回promise執行的結果傳遞給下一次的then中
- resolvePromise函式,返回的結果和promise是同一個,既不會成功也不會失敗,我們就報一個型別錯誤提示一下
以上我們已經實現了promise的鏈式呼叫了,但這還不夠promise有一種情況是在then中什麼都不傳的情況,還繼續鏈式呼叫
let p = new Promise(function (resolve, reject) {
reject(999);
});
p.then().then().then(function (data) {
console.log(data); // 999
}, function (err) {
console.log(err);
});
複製程式碼
這就是promise中值的穿透,該實現是在then中,那麼我們也對then方法加以改良去實現一下
Promise.prototype.then = function(onFulfilled, onRejected) {
+ onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) {
return value
};
+ onRejected = typeof onRejected === 'function' ? onFulfilled : function (err) {
throw err
};
const self = this;
let promise2;
};
複製程式碼
其實很簡單,就是在then的時候如果你不給我傳回撥函式,那麼我就判斷一下,確實沒有傳,那我就給你新建立一個函式然後在成功回撥的時候把值傳到下一個then中去
另外在promise規範中要求,所有的onFulfilled和onRejected都需要非同步執行,所以我們要給他們加上非同步,顯然這個更容易實現了,看下面程式碼
Promise.prototype.then = function(onFulfilled, onRejected) {
if (self.status === 'fulfilled') {
promise2 = new Promise(function (resolve, reject) {
// 當成功或失敗執行時,有異常那麼返回的promise應該處於失敗態
// 用try/catch來防止返回異常的情況
// 加上setTimeout實現非同步呼叫
+ setTimeout(function () {
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e);
}
});
});
}
if (self.status === 'rejected') {
promise2 = new Promise(function (resolve, reject) {
+ setTimeout(function () {
try {
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e);
}
});
});
}
};
if (self.status === 'pending') {
// 將成功的回撥新增到陣列中
promise2 = new Promise(function (resolve, reject) {
self.onFulfilledCb.push(function () {
+ setTimeout(function () {
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e);
}
});
});
self.onRejectedCb.push(function () {
+ setTimeout(function () {
try {
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e);
}
});
});
}
return promise2;
複製程式碼
好了,我們以上就實現了promise中最重要的then方法了,寫的不好請多理解吧。
不過既然把then都寫完了,那接下來再寫幾個其他的,寫多點也讓大家一起來研究研究。
其他方法
捕獲錯誤的catch方法
先來看下用法,其實這個和then方法裡失敗的回撥onRejected是一樣的
let p = new Promise(function(resolve, reject) {
reject('錯錯錯');
});
p.then(function(data){
console.log(data);
}).catch(function(e){
console.log(e); // '錯錯錯'
});
複製程式碼
下面上程式碼:
// 捕獲錯誤的方法
Promise.prototype.catch = function(callback) {
// 也是呼叫then方法,給成功的回撥傳一個null,給失敗的回撥傳入callback
return this.then(null, callback);
};
複製程式碼
Promise類自身也有一些方法,常用的有all, race, resolve, reject
都寫到這裡了,那就繼續寫下去,廢話不多說,開擼!!!
all方法
Promise.all()的用法
let p = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('北京');
}, 1000);
});
let p2 = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('南京');
}, 200);
});
let p3 = new Promise(function(resolve, reject) {
resolve('東京');
});
Promise.all([p, p2, p3]).then(function(data) {
// all方法是所有的promise都成功後才會成功
// 按照傳入all裡的順序依次執行,p裡面的定時器只是執行完成的時間而已,不影響all裡輸出順序
// 如:p這個需要等1秒後才會返回成功態,p2就需要等待1秒時間
console.log(data); // [ '北京', '南京', '東京' ]
});
複製程式碼
上面知道了用法,下面就來寫實現,客官且留步繼續看下去
Promise.all = function(items) {
return new Promise(function(resolve, reject) {
let res = []; // 用來儲存成功函式返回的值
let num = 0; // 記錄都返回成功的數字
let len = items.length; // 陣列的長度
// 對陣列進行遍歷
for (let i = 0; i < len; i++) {
items[i].then(function(data) {
res[i] = data;
if (++num === len) {
resolve(res);
}
}, reject);
}
});
};
複製程式碼
如此這般,這般如此,就實現了all
再接再厲還有race方法
那麼race方法顧名思義,race就是比賽,就是在比誰快,誰先成功,返回的就是誰的成功資料
race方法
來看一下怎麼用
let p = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('北京');
}, 1000);
});
let p2 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('南京');
}, 500);
});
let p3 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('東京');
});
});
Promise.race([p, p2, p3]).then(function (data) {
// p3最先執行完返回成功,所以data就為p3的值,其他的p,p2都不返回
console.log(data); // '東京'
});
複製程式碼
那麼,不囉嗦了,寫吧
Promise.race = function(items) {
return new Promise(function(resolve, reject) {
for (let i = 0; i < items.length; i++) {
items[i].then(resolve, reject);
}
});
};
複製程式碼
以上就實現了race方法,那麼一鼓作氣再寫下resolve和reject吧
這兩個方法看起來就非常簡單,因為之前的鋪墊已經很好了基本屬於直接用的狀態 還是先來看下用法吧
resolve和reject方法
// 成功
Promise.resolve([1,2,3,4]).then(function(data) {
console.log(data); // [1,2,3,4]
});
// 失敗
Promise.reject('err!').then(function () {}, function (err) {
console.log(err); // 'err!'
});
複製程式碼
來,快點,開寫,不猶豫
Promise.resolve = function(value) {
return new Promise(function(resolve, reject) {
resolve(value);
});
};
Promise.reject = function (reason) {
return new Promise(function (resolve, reject) {
reject(reason);
});
};
複製程式碼
完成!!!
各位客官,是不是覺得感覺身體被掏空了,寫了這麼多終於完事了,可以來梳理一下了。 錯了,還有最後一個方法沒寫,它就是defer
defer方法的作用是什麼呢?
首先它不需要寫new來生成promise例項了,
其次是因為promise的測試庫promises-aplus-tests需要寫這麼一個方法,哈哈哈
看下怎麼用吧,其實和Q是一樣的
function read() {
let defer = Promise.defer(); // Q裡寫的是Q.defer()
require('fs').readFile('./2.promise/1.txt', 'utf8', function (err, data) {
if(err) defer.reject(err);
defer.resolve(data);
})
return defer.promise;
}
read().then(function (data) {
console.log(data)
},function(err){
console.log(err);
})
複製程式碼
為了測試庫的需要,實現最後一個吧
Promise.defer = Promise.deferred = function() {
let def = {};
def.promise = new Promise(function(resolve, reject) {
def.resolve = resolve;
def.reject = reject;
});
return def;
}
複製程式碼
終於,完成了所有的實現了。實屬不易,也謝謝大家堅持看完,希望有所收穫吧!哈哈
- 最後的最後,當各位客官也在嘗試著實現promise的時候
- 別忘記把寫完的程式碼進行測試一下
- promiseA+規範也有一個專門的測試庫來跑通你實現的promise到底如何
- 全域性安裝 npm i promises-aplus-tests -g
- promises-aplus-tests 專案.js
- 如果跑完全是√,那麼恭喜你,你又多了一項技能了