使用Promise
能解決回調地獄,多個異步請求等問題。那麼它是怎麼實現的呢?
讓咱們一塊兒來實現一下吧jquery
首先,咱們要知道:npm
class Promise {
constructor(executor) { //executor執行器
this.status = 'pending'; //默認等待狀態
this.value = undefined; //成功的值
this.reason = undefined //失敗的原用
let resolve = (value) => {
if (this.status === 'pending') {
this.status = 'resolved'; //成功
this.value = value;
}
}
let reject = (reason) => {
if (this.status === 'pending') {
this.status = 'rejected'; //失敗
this.reason = reason;
}
}
executor(resolve, reject); //默認上執行器執行
}
then(onFufilled, onRejected) {
if (this.status === 'resolved') { //成功態
onFufilled(this.value);
}
if (this.status === 'rejected') { //失敗態
onRejected(this.reason);
}
}
}
module.exports = Promise
複製代碼
以上,咱們就簡單的實現了一個同步的promise。 測試一下吧數組
let Promise = require('./myPromise.js')
let promise = new Promise((resolve, reject) => {
resolve('hello')
})
promise.then((data) => {
console.log(data)
}, (err) => {
console.log(err)
})
複製代碼
打印結果:promise
可是,咱們知道,promise主要解決的是異步回調問題。因此,異步調用必須實現起來。bash
當異步調用時,當調用實例的then時,狀態可能還處於pending狀態,這時咱們須要在實例上定義兩個存放成功和失敗方法的數組,把須要執行的方法分別放到對應的數組裏,等到異步時間到達的時候,再去執行對應數組裏的方法。異步
class Promise {
constructor(executor) { //executor執行器
this.status = 'pending'; //默認等待狀態
this.value = undefined; //成功的值
this.reason = undefined //失敗的原用
+ //存放then成功,失敗的回調的數組
this.onResovleCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = (value) => {
if (this.status === 'pending') {
this.status = 'resolved'; //成功
this.value = value;
+ this.onResovleCallbacks.forEach(fn => fn());
}
}
let reject = (reason) => {
if (this.status === 'pending') {
this.status = 'rejected'; //失敗
this.reason = reason;
+ this.onRejectedCallbacks.forEach(fn => fn());
}
}
executor(resolve, reject); //默認上執行器執行
}
then(onFufilled, onRejected) {
if (this.status === 'resolved') { //成功態
onFufilled(this.value);
}
if (this.status === 'rejected') { //失敗態
onRejected(this.reason);
}
+ if (this.status === 'pending') {
this.onResovleCallbacks.push(() => {
onFufilled(this.value)
});
this.onRejectedCallbacks.push(() => {
onRejected(this.reason)
})
}
}
}
module.exports = Promise
複製代碼
以上,咱們就實現了promise的異步調用。 測試一下吧函數
let Promise = require('./myPromise.js')
let promise = new Promise((resolve, reject) => {
setTimeout(function(){
resolve('hello')
},100)
})
promise.then((data) => {
console.log(data)
}, (err) => {
console.log(err)
})
複製代碼
打印結果: 測試
當執行的時候拋出異常時,咱們應該讓它當狀態變爲rejected,去執行then的錯誤方法。 這時候,須要在執行器執行的時候 捕獲一下錯誤,並做出rejected處理ui
try {
executor(resolve, reject);
} catch (e) { //捕獲到異常時,直接走失敗
reject(e);
}
複製代碼
測試一下吧this
let Promise = require('./myPromise.js')
let promise = new Promise((resolve, reject) => {
throw new Error('❌')
})
promise.then((data) => {
console.log(data)
}, (err) => {
console.log(err)
})
複製代碼
打印結果:
說到鏈式調用,我們接觸最多的就是jquery,jquery實現鏈式調用是靠的是返回this,promise實現鏈式調用是否是也返回this呢?答案是,
NO !它實現鏈式調用靠的是返回一個新的promise
。 在then方法裏,不管promise處於哪一種狀態,執行完後,都返回一個新的promise。
then(onFufilled, onRejected) {
+ let promise2; //返回的新promise
+ promise2 = new Promise((resolve, reject) => {
if (this.status === 'resolved') {
onFufilled(this.value);
}
if (this.status === 'rejected') {
onRejected(this.reason);
}
if (this.status === 'pending') {
this.onResovleCallbacks.push(() => {
onFufilled(this.value)
});
this.onRejectedCallbacks.push(() => {
onRejected(this.reason)
})
}
});
+ return promise2;
}
複製代碼
在then中,不管是成功的回調仍是失敗的回調,只要返回告終果就會走下一個then中的成功,若是有錯誤,就會走下一個then的失敗回調。即:下一個then的狀態跟上一個then執行時候的狀態無關。 因此,在then執行的時候,
onFufilled, onRejected
可能會出錯,這時候,咱們須要捕獲錯誤,並處理成失敗
promise2 = new Promise((resolve, reject) => {
if (this.status === 'resolved') {
try {
onFufilled(this.value);
} catch (e) {
reject(e)
}
}
if (this.status === 'rejected') {
try {
onRejected(this.reason);
} catch (e) {
reject(e)
}
}
if (this.status === 'pending') {
this.onResovleCallbacks.push(() => {
try {
onFufilled(this.value)
} catch (e) {
reject(e);
}
});
this.onRejectedCallbacks.push(() => {
try {
onRejected(this.reason)
} catch (e) {
reject(e);
}
})
}
});
複製代碼
測試一下吧
let promise = new Promise((resolve, reject) => {
resolve('hello')
})
promise.then((data) => {
console.log(data)
throw new Error('🙅')
}, (err) => {
console.log(err)
}).then((data) => {
console.log(data)
}, (err) => {
console.log('🙅' + err)
})
複製代碼
打印結果:
try {
//x是上一個promise返回值,多是一個普通值,也多是一個promise;x也多是別人的promise,咱們能夠寫一個方法,統一處理
let x=onFufilled(this.value);
//入參:下一次then的實例promise2,此次返回值x,promise2的成功方法,promise2的失敗方法
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e)
}
複製代碼
下面來實現resolvePromise
,用來處理多套promise共用的狀況:
/*
* resolvePromise
* @Parameters
* promise2: 下一次then的實例promise2
* x: 此次返回值x
* resolve: promise2的成功方法
* reject: promise2的失敗方法
*/
function resolvePromise(promise2, x, resolve, reject) {
//x多是別人的promise,因此儘量的容許別人瞎寫
if (promise2 === x) { //返回的結果和promise是同一個,那麼永遠不會成功
return reject(new TypeError('循環引用'));
}
let called;//是否調用過成功或失敗
// 看x是否是promise。promise應該是一個對象
if (x != null && (typeof x === 'object' || typeof x === 'function')) { //多是promise
try {
let then = x.then; // 若是是對象 就試着取一下then方法 若是有then,認爲它是promise
if (typeof then === 'function') { // 若是then是函數,是promise
then.call(x, y => {
// 成功和失敗只能調用一個
if (called) return;
called = true;
// resolve的結果依舊是promise 那就繼續解析
resolvePromise(promise2, y, resolve, reject);
}, r => {
if (called) return;
called = true;
reject(r); // 失敗了就失敗了
})
} else {
resolve(x); // 直接成功便可
}
} catch (e) { // 取then出錯了那就不要在繼續執行了
if (called) return;
called = true;
reject(e);
}
} else { //普通值 讓promise2直接變成成功態
resolve(x);
}
};
複製代碼
測試一下吧
let promise = new Promise((resolve, reject) => {
resolve('hello')
})
promise.then((data) => {
console.log(data)
throw new Error('🙅')
}, (err) => {
console.log(err)
}).then((data) => {
console.log(data)
}, (err) => {
console.log('🙅' + err)
})
複製代碼
打印結果:
let promise = new Promise((resolve, reject) => {
resolve('hello')
})
promise.then((data) => {
console.log(data)
return new Promise((resolve, reject) => {
resolve('👋')
})
}, (err) => {
console.log(err)
}).then((data) => {
console.log(data)
}, (err) => {
console.log('🙅' + err)
})
複製代碼
打印結果:
以上,咱們的promise好像已經差很少了,可是還有一個問題,須要處理。源碼能夠在hen中實現什麼都不傳。promise中管這種現象叫,值的穿透
。 所以,咱們須要在then方法裏,對then方法的入參進行容錯處理:
onFufilled = typeof onFufilled === 'function' ? onFufilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err};
複製代碼
測試一下吧
let Promise = require('./myPromise.js')
let promise = new Promise((resolve, reject) => {
resolve('hello')
})
promise.then().then((data) => {
console.log(data)
}, (err) => {
console.log('🙅' + err)
})
複製代碼
打印結果:
另外,promise規範中要求,全部的onFufilled
和onRejected
都須要異步執行,若是不加異步可能形成測試的不穩定性,因此咱們給執行這兩個方法執行的地方都加上異步方法。
if (this.status === 'resolved') {
setTimeout(() => {
try {
let x=onFufilled(this.value);
resolvePromise(promise2,x,resolve,reject);
} catch (e) {
reject(e)
}
}, 0);
}
複製代碼
class Promise {
constructor(executor) { //executor執行器
this.status = 'pending'; //默認等待狀態
this.value = undefined; //成功的值
this.reason = undefined //失敗的原用
this.onResovleCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = (value) => {
if (this.status === 'pending') {
this.status = 'resolved'; //成功
this.value = value;
this.onResovleCallbacks.forEach(fn => fn());
}
}
let reject = (reason) => {
if (this.status === 'pending') {
this.status = 'rejected'; //失敗
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
}
try {
executor(resolve, reject); //默認上執行器執行
} catch (e) { //捕獲到異常時,直接走失敗
reject(e);
}
}
then(onFufilled, onRejected) {
onFufilled = typeof onFufilled === 'function' ? onFufilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => {
throw err
};
function resolvePromise(promise2, x, resolve, reject) {
//x多是別人的promise,因此儘量的容許別人瞎寫
if (promise2 === x) { //返回的結果和promise是同一個,那麼永遠不會成功
return reject(new TypeError('循環引用'));
}
//
let called;
// 看x是否是promise。promise應該是一個對象
if (x != null && (typeof x === 'object' || typeof x === 'function')) { //多是promise
try {
let then = x.then; // 若是是對象 我就試着取一下then方法 若是有then,認爲它是promise
if (typeof then === 'function') { // then是函數,是promise
then.call(x, y => {
// 成功和失敗只能調用一個
if (called) return;
called = true;
// resolve的結果依舊是promise 那就繼續解析
resolvePromise(promise2, y, resolve, reject);
}, r => {
if (called) return;
called = true;
reject(r); // 失敗了就失敗了
})
} else {
resolve(x); // 直接成功便可
}
} catch (e) { // 取then出錯了那就不要在繼續執行了
if (called) return;
called = true;
reject(e);
}
} else { //普通值 讓promise2直接變成成功態
resolve(x);
}
};
let promise2; //返回的新promise
promise2 = new Promise((resolve, reject) => {
if (this.status === 'resolved') {
setTimeout(() => {
try {
let x = onFufilled(this.value); //x是上一個promise返回值,多是一個普通值,也多是一個promise;x也多是別人的promise,咱們能夠寫一個方法,統一處理
resolvePromise(promise2, x, resolve, reject); //下一次then的實例promise2,此次返回值x,promise2的成功方法,promise2的失敗方法
} catch (e) {
reject(e)
}
}, 0);
}
if (this.status === 'rejected') {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
}, 0)
}
if (this.status === 'pending') {
this.onResovleCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFufilled(this.value)
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
})
}
});
return promise2;
}
}
module.exports = Promise
複製代碼
以上,咱們基本完成了一個本身的promise庫。 最後,看看這個庫可不可行,那麼就須要測試。官方給出了一個測試的庫promises-aplus-tests
,它會幫咱們校驗,這個庫是否可行。另外測試須要用defer
,它是promise的語法糖。
Promise.defer = Promise.deferred = function () {
let dfd = {}
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}
複製代碼
安裝
npm install -g promises-aplus-tests
複製代碼
執行
promises-aplus-tests ./myPromise.js
複製代碼
以上,咱們就本身完成了一個基於Promise A+規範的Promise。