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 。