咱們一般都會說爲了解決回調地獄。編程
那好,什麼是回調地獄:promise
多層嵌套的問題。 每種任務的處理結果存在兩種可能性(成功或失敗),那麼須要在每種任務執行結束後分別處理這兩種可能性。bash
智者見者,仁者見仁,不一樣的人就會有不一樣的Promise實現,可是你們都必須遵循promise a+ 規範 ,那符合規範的一個Promise究竟是長什麼樣的?異步
new Promise(() => {
console.log(1);
});
複製代碼
ok,基於以上所述咱們寫一個最基本的promise函數
const PENDING = 'PENDING';
const RESOLVED = 'RESOLVED';
const REJECTED = 'REJECTED';
class Promise {
constructor(executor) {
this.status = PENDING; // 宏變量, 默認是等待態
this.value = undefined; // then方法要訪問到因此放到this上
this.reason = undefined; // then方法要訪問到因此放到this上
let resolve = (value) => {
if (this.status === PENDING) {// 保證只有狀態是等待態的時候才能更改狀態
this.value = value;
this.status = RESOLVED;
}
};
let reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
}
};
// 執行executor傳入咱們定義的成功和失敗函數:把內部的resolve和reject傳入executor中用戶寫的resolve, reject
try {
executor(resolve, reject);
} catch(e) {
console.log('catch錯誤', e);
reject(e); //若是內部出錯 直接將error手動調用reject向下傳遞
}
}
then(onfulfilled, onrejected) {
if (this.status === RESOLVED) {
onfulfilled(this.value);
}
if (this.status === REJECTED) {
onrejected(this.reason);
}
}
}
module.exports = Promise;
複製代碼
咱們平時使用promise基本上都是把一些請求接口的邏輯封裝在一個promise內,當請求成功後把數據resolve出去,或者請求失敗以後把錯誤reject出去,也就是說promise必須支持異步,那咱們想想如何支持異步呢?測試
答案就是發佈訂閱者模式,看代碼實現吧ui
const PENDING = 'PENDING';
const RESOLVED = 'RESOLVED';
const REJECTED = 'REJECTED';
class Promise {
constructor(executor) {
this.status = PENDING; // 宏變量, 默認是等待態
this.value = undefined; // then方法要訪問到因此放到this上
this.reason = undefined; // then方法要訪問到因此放到this上
this.onResolvedCallbacks = [];// 專門存放成功的回調函數
this.onRejectedCallbacks = [];// 專門存放成功的回調函數
let resolve = (value) => {
if (this.status === PENDING) {// 保證只有狀態是等待態的時候才能更改狀態
this.value = value;
this.status = RESOLVED;
// 須要讓成功的方法依次執行
this.onResolvedCallbacks.forEach(fn => fn());
}
};
let reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
// 須要讓失敗的方法依次執行
this.onRejectedCallbacks.forEach(fn => fn());
}
};
// 執行executor傳入咱們定義的成功和失敗函數:把內部的resolve和reject傳入executor中用戶寫的resolve, reject
try {
executor(resolve, reject);
} catch(e) {
console.log('catch錯誤', e);
reject(e); //若是內部出錯 直接將error手動調用reject向下傳遞
}
}
then(onfulfilled, onrejected) {
if (this.status === RESOLVED) {
onfulfilled(this.value);
}
if (this.status === REJECTED) {
onrejected(this.reason);
}
// 處理異步的狀況
if (this.status === PENDING) {
// this.onResolvedCallbacks.push(onfulfilled); 這種寫法能夠換成下面的寫法,多包了一層,這叫面向切片編程,能夠加上本身的邏輯
this.onResolvedCallbacks.push(() => {
// TODO ... 本身的邏輯
onfulfilled(this.value);
});
this.onRejectedCallbacks.push(() => {
// TODO ... 本身的邏輯
onrejected(this.reason);
});
}
}
}
module.exports = Promise;
複製代碼
寫點測試代碼試試看吧this
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('xxx');
}, 1000);
});
// 發佈訂閱模式應對異步 支持一個promise能夠then屢次
promise.then((res) => {
console.log('成功的結果1', res);
}, (error) => {
console.log(error);
});
promise.then((res) => {
console.log('成功的結果2', res);
}, (error) => {
console.log(error);
});
複製代碼
結果spa
成功的結果1 xxx
成功的結果2 xxx
複製代碼
到此,咱們其實作了不多的工做但已經實現了promise最基本也是最核心的功能了。接下來咱們加上鍊式調用,這裏面可能比較繞,但只要咱們記住下面幾條就會很輕鬆掌握其中原理:code
接下來看看代碼理解下吧
const PENDING = 'PENDING';
const RESOLVED = 'RESOLVED';
const REJECTED = 'REJECTED';
function resolvePromise(promise2, x, resolve, reject) {
if((typeof x === 'object' && x != null) || typeof x === 'function') {
// 有多是promise, 若是是promise那就要有then方法
let then = x.then;
if (typeof then === 'function') { // 到了這裏就只能認爲他是promise了
// 若是x是一個promise那麼在new的時候executor就當即執行了,就會執行他的resolve,那麼數據就會傳遞到他的then中
then.call(x, y => {// 當前promise解析出來的結果可能仍是一個promise, 直到解析到他是一個普通值
resolvePromise(promise2, y, resolve, reject);// resolve, reject都是promise2的
}, r => {
reject(r);
});
} else {
// 出現像這種結果 {a: 1, then: 1}
resolve(x);
}
} else {
resolve(x);
}
}
class Promise {
constructor(executor) {
this.status = PENDING; // 宏變量, 默認是等待態
this.value = undefined; // then方法要訪問到因此放到this上
this.reason = undefined; // then方法要訪問到因此放到this上
// 專門存放成功的回調函數
this.onResolvedCallbacks = [];
// 專門存放成功的回調函數
this.onRejectedCallbacks = [];
let resolve = (value) => {
if (this.status === PENDING) { // 保證只有狀態是等待態的時候才能更改狀態
this.value = value;
this.status = RESOLVED;
// 須要讓成功的方法一次執行
this.onResolvedCallbacks.forEach(fn => fn());
}
};
let reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
// 須要讓失敗的方法一次執行
this.onRejectedCallbacks.forEach(fn => fn());
}
};
// 執行executor 傳入成功和失敗:把內部的resolve和 reject傳入executor中用戶寫的resolve, reject
try {
executor(resolve, reject); // 當即執行
} catch (e) {
console.log('catch錯誤', e);
reject(e); //若是內部出錯 直接將error 手動調用reject向下傳遞
}
}
then(onfulfilled, onrejected) {
// 爲了實現鏈式調用,建立一個新的promise
let promise2 = new Promise((resolve, reject) => {
if (this.status === RESOLVED) {
// 執行then中的方法 可能返回的是一個普通值,也多是一個promise,若是是promise的話,須要讓這個promise執行
// 使用宏任務把代碼放在一下次執行,這樣就能夠取到promise2,爲何要取到promise2? 這裏在以後會介紹到
setTimeout(() => {
try {
let x = onfulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) { // 一旦執行then方法報錯就走到下一個then的失敗方法中
console.log(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) {
// 這時候executor確定是有異步邏輯
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onfulfilled(this.value);
// 注意這裏傳入的是promise2的resolve和reject
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;
複製代碼
主要就是多了resolvePromise這麼一個函數,用來遞歸處理then內部回調函數執行後的結果,它有4個參數:
到了這裏基本上完整的Promise已經實現了,接下來咱們作一些完善工做。
catch方法其實就是沒有成功回調的then方法,這個很好理解,由於一旦失敗以後就會調用reject,最終都會走到then方法的失敗回調中,只是簡單的把then方法換個名字而已。
catch(errCallback) {
return this.then(null, errCallback);
}
複製代碼
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : v => v;
onrejected = typeof onrejected === 'function' ? onrejected : error => { throw error };
複製代碼
上面曾問到resolvePromise第一個參數promise2到底有什麼用?其實很簡單就是爲了符合promise a+ 規範。下面咱們來完善resolvePromise
function resolvePromise(promise2, x, resolve, reject) {
// 1)不能引用同一個對象 可能會形成死循環
if (promise2 === x) {
return reject(new TypeError('[TypeError: Chaining cycle detected for promise #<Promise>]----'));
}
let called;// promise的實現可能有多個,但都要遵循promise a+規範,咱們本身寫的這個promise用不上called,可是爲了遵循規範才加上這個控制的,由於別人寫的promise可能會有屢次調用的狀況。
// 2)判斷x的類型,若是x是對象或者函數,說明x有多是一個promise,不然就不多是promise
if((typeof x === 'object' && x != null) || typeof x === 'function') {
// 有多是promise promise要有then方法
try {
// 由於then方法有多是getter來定義的, 取then時有風險,因此要放在try...catch...中
// 別人寫的promise多是這樣的
// Object.defineProperty(promise, 'then', {
// get() {
// throw new Error();
// }
// })
let then = x.then;
if (typeof then === 'function') { // 只能認爲他是promise了
// x.then(()=>{}, ()=>{}); 不要這麼寫,以防如下寫法形成報錯, 並且也能夠防止屢次取值
// let obj = {
// a: 1,
// get then() {
// if (this.a++ == 2) {
// throw new Error();
// }
// console.log(1);
// }
// }
// obj.then;
// obj.then
// 若是x是一個promise那麼在new的時候executor就當即執行了,就會執行他的resolve,那麼數據就會傳遞到他的then中
then.call(x, y => {// 當前promise解析出來的結果可能仍是一個promise, 直到解析到他是一個普通值
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);// resolve, reject都是promise2的
}, r => {
if (called) return;
called = true;
reject(r);
});
} else {
// {a: 1, then: 1}
resolve(x);
}
} catch(e) {// 取then出錯了 有可能在錯誤中又調用了該promise的成功或則失敗
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
複製代碼
對於1)不能引用同一個對象 可能會形成死循環,咱們舉個例子:
let promise = new Promise((resolve, reject) => {
resolve('hello');
});
let promise2 = promise.then(() => {
return promise2;
});
promise2.then(() => {}, (err) => {
console.log(err);
});
複製代碼
就會報下面的錯
[TypeError: Chaining cycle detected for promise #<Promise>]
複製代碼
由於promise的then方法執行的時候建立了promise2,這個時候promise2狀態是pending, 而成功回調裏又返回promise2,既然返回的結果是一個promise那就繼續解析嘗試在它的then方法中拿到這個promise的結果,此時promise2的狀態依然是pending,那麼執行promise2.then方法只會添加訂閱,而一直得不到resolve, 因而本身等待本身就死循環了。
有這麼一種狀況好比
new Promise((resolve, reject) => {
resolve(new Promise((resolve, reject) => {
resolve('hello');
}));
});
複製代碼
咱們上面實現的代碼就沒法完成這麼一個操做了,修改很簡單
let resolve = (value) => {
// 判斷value的值
if (value instanceof Promise) {
value.then(resolve, reject);//resolve和reject都是當前promise的, 遞歸解析直到是普通值, 這裏的resolve,reject都取的到,由於resolve的執行是在這兩個函數執行以後,這裏遞歸是防止value也是一個promise
return;
}
if (this.status === PENDING) { // 保證只有狀態是等待態的時候才能更改狀態
this.value = value;
this.status = RESOLVED;
// 須要讓成功的方法一次執行
this.onResolvedCallbacks.forEach(fn => fn());
}
};
複製代碼
下面給出完整代碼
const PENDING = 'PENDING';
const RESOLVED = 'RESOLVED';
const REJECTED = 'REJECTED';
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('[TypeError: Chaining cycle detected for promise #<Promise>]----'));
}
let called;
if((typeof x === 'object' && x != null) || typeof x === 'function') {
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, y => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, r => {
if (called) return;
called = true;
reject(r);
});
} else {
resolve(x);
}
} catch(e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
class Promise {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = (value) => {
if (value instanceof Promise) {
value.then(resolve, reject);
return;
}
if (this.status === PENDING) {
this.value = value;
this.status = RESOLVED;
this.onResolvedCallbacks.forEach(fn => fn());
}
};
let reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
then(onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : v => v;
onrejected = typeof onrejected === 'function' ? onrejected : error => { throw error };
let promise2 = new Promise((resolve, reject) => {
if (this.status === RESOLVED) {
setTimeout(() => {
try {
let x = onfulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
console.log(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.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onfulfilled(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;
}
catch(errCallback) {
return this.then(null, errCallback);
}
}
module.exports = Promise;
複製代碼
到了這裏咱們的promise就算是告一段落了,接下來咱們會用promise來實戰解決回調地獄,而後實現Promise.resolve, Promise.reject,Promise.all, Promise.race, Promise.finally 。