Обещание (1) - обещание почерка

Promise

Почему обещания?

Мы обычно говорим, чтобы решить ад обратного вызова.

Ну и что такое callback hell:

Проблема многоуровневой вложенности. Существует две возможности (успех или неудача) для результата обработки каждой задачи, поэтому эти две возможности необходимо обрабатывать отдельно после выполнения каждой задачи.

Как реализовать обещание?

Мудрые видят смотрящего, доброжелательные видят благожелательность, у разных людей будут разные реализации обещаний, но все должны следовать спецификации обещания а+, как выглядит обещание, соответствующее спецификации?

  1. Обещание — это класс, класс должен передать исполнителю исполнителя, который по умолчанию будет выполняться немедленно, как и в следующем примере, он немедленно выведет 1
new Promise(() => {
	console.log(1);
});
  1. Обещание предоставляет два внутренних метода. Обратите внимание, что его нет в объекте-прототипе. Эти два метода будут переданы пользователю и могут изменить состояние обещания.
  2. Промис имеет три состояния:
    1. В ОЖИДАНИИ
    2. Success (RESOLVED) возвращает успешный результат, если результат не записан, возвращает undefined
    3. Failed (REJECTED) возвращает причину сбоя, если причина не написана, возвращает undefined
  3. Обещание может перейти только от ожидания к успеху или от ожидания к провалу.
  4. Каждый экземпляр промиса должен иметь метод then, который является обратным вызовом для успеха и неудачи соответственно.

хорошо, на основе вышесказанного пишем базовый промис

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;

Обычно мы используем промисы, чтобы в основном инкапсулировать логику некоторых интерфейсов запроса в промис.Когда запрос успешен, данные разрешаются или ошибка отклоняется после сбоя запроса.То есть промис должен поддерживать асинхронность, тогда мы думаем об этом Как бы вы хотели поддерживать асинхронность?

Ответ — режим публикации-подписки, см. реализацию кода

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;

Напишите тестовый код и попробуйте

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);
});

результат

成功的结果1 xxx
成功的结果2 xxx

На данный момент мы на самом деле сделали очень мало работы, но реализовали самые основные и основные функции промисов. Затем мы добавим цепной вызов, который может быть довольно извилистым, но если мы помним следующее, будет легко понять принцип:

  1. Тогда метод должен вернуть обещание обеспечить привязные звонки.
  2. Если выполнение внутренней функции обратного вызова все еще является обещанием, вы выйдете из этого обещания Resolve.
  3. Любое обещание должно быть разрешено, прежде чем оно сможет перейти к методу then для создания следующего обещания.
  4. Когда происходит обратный вызов успеха? затем возвращает нормальное значение или успешное обещание
  5. Когда следует отказаться от обратного вызова? Вернуть невыполненное обещание или создать исключение

Давайте посмотрим на код, чтобы понять


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, которая используется для рекурсивной обработки результата выполнения тогда внутренней callback-функции, имеет 4 параметра:

  1. promise2: это только что сгенерированный промис.Что касается того, почему промис 2 пропускается, это будет представлено позже.
  2. x: цель, с которой мы имеем дело
  3. разрешение: разрешение обещания2, после выполнения состояние обещания2 становится успешным, и вы можете получить окончательный результат в успешном обратном вызове его метода then.
  4. Отклонение: отклонение promise2, состояние promise2 становится неудачным после выполнения, и причина сбоя получается в обратном вызове сбоя его then-метода.

На данный момент в основном реализован полный Promise, а затем мы проведем некоторые улучшения.

метод ловли

Метод catch на самом деле является методом then без успешного обратного вызова Это легко понять, потому что, как только он завершится ошибкой, будет вызван метод reject, и в конечном итоге он перейдет к обратному вызову с ошибкой метода then, просто измените имя тогдашний метод.

catch(errCallback) {
    return this.then(null, errCallback);
}

Граничное суждение об успешном обратном вызове и ошибочном обратном вызове

onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : v => v;
onrejected = typeof onrejected === 'function' ? onrejected : error => { throw error };

Позвольте решитьПромизировать соответствовать

Какая польза от первого параметра promise2 в resolvePromise? На самом деле соблюсти спецификацию promise a+ очень просто. Давайте улучшим решениеОбещание

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>]

Потому что promise2 создается, когда выполняется метод then обещания.В это время состояние promise2 находится в ожидании, и promise2 возвращается в успешном обратном вызове.Поскольку возвращаемый результат является обещанием, продолжайте синтаксический анализ и попытайтесь получить обещание этот промис в своем методе then.В результате в это время состояние promise2 все еще находится в ожидании, тогда выполнение метода promise2.then только добавит подписки, но он никогда не был разрешен, поэтому он будет ждать себя и его будет бесконечный цикл.

решимость также является обещанием

Есть ситуация, такая как

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.resolve, Promise.reject, Promise.all, Promise.race, Promise.finally .