Promise-手寫Promise

爲何會有Promise?

咱們一般都會說爲了解決回調地獄。編程

那好,什麼是回調地獄:promise

多層嵌套的問題。 每種任務的處理結果存在兩種可能性(成功或失敗),那麼須要在每種任務執行結束後分別處理這兩種可能性。bash

怎麼實現一個Promise?

智者見者,仁者見仁,不一樣的人就會有不一樣的Promise實現,可是你們都必須遵循promise a+ 規範 ,那符合規範的一個Promise究竟是長什麼樣的?異步

  1. Promise是一個類, 類中須要傳入一個executor執行器, 默認會當即執行,就像下面這樣會當即打印出1
new Promise(() => {
	console.log(1);
});
複製代碼
  1. promise有內部會提供兩個方法,注意不是原型對象上的,這兩個方法會傳給用戶,能夠更改promise的狀態。
  2. promise有三個狀態:
    1. 等待(PENDING)
    2. 成功(RESOLVED)返回成功的結果,若是不寫結果返回undefined
    3. 失敗(REJECTED)返回失敗的緣由,若是不寫緣由返回undefined
  3. promise只會從等待變爲成功或者從等待變爲失敗。
  4. 每一個promise實例上都要有一個then方法, 分別是成功和失敗的回調。

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

  1. then方法返回的必須是一個promise,這樣才能保證鏈式調用。
  2. 若是then內部的回調函數執行結果依然是一個promise那就把這個promise的結果resolve出去。
  3. 任何一個promise必須是resolve以後才能走到它then方法,從而建立下一個的promise。
  4. 何時走成功回調?then中返回一個普通值或者一個成功的promise
  5. 何時走失敗回調?返回一個失敗的promise,或者拋出異常

接下來看看代碼理解下吧

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個參數:

  1. promise2: 就是新生成的promise,這裏至於爲何要把promise2傳過來後面會介紹。
  2. x: 咱們要處理的目標
  3. resolve: promise2的resolve, 執行以後promise2的狀態就變爲成功了,就能夠在它的then方法的成功回調中拿到最終結果。
  4. reject: promise2的reject, 執行以後promise2的狀態就變爲失敗,在它的then方法的失敗回調中拿到失敗緣由。

到了這裏基本上完整的Promise已經實現了,接下來咱們作一些完善工做。

catch方法

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符合規範

上面曾問到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, 因而本身等待本身就死循環了。

resolve的也是promise

有這麼一種狀況好比

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 。

相關文章
相關標籤/搜索