Why is there a Promise?

We often say that in order to solve callback hell.

Well, what is callback hell:

The problem of multi-layer nesting. There are two possibilities (success or failure) for each task. You need to handle the two possibilities respectively after each task is executed.

How to fulfill a Promise?

Wise people see, people see, different people will have different Promise implementation, but everyone must follow the Promise A + specification, that accord with the specification of a Promise exactly what does it look like?

  1. Promise is a class that needs to pass in an Executor executor, which by default will execute immediately, printing 1 as shown below
new Promise(() => {
	console.log(1);
});
Copy the code
  1. The promise provides two methods internally, not on the prototype object, that are passed to the user to change the state of the promise.
  2. Promise has three states:
    1. Wait (PENDING)
    2. RESOLVED returns a successful result or undefined if no result is written
    3. Failed (REJECTED) Returns the cause of the failure, and undefined if you do not write the cause
  3. Promises can only change from waiting to success or waiting to failure.
  4. Each promise instance should have a THEN method, which is a successful and a failed callback.

Ok, with that in mind, let’s write a basic promise

const PENDING = 'PENDING';
const RESOLVED = 'RESOLVED';
const REJECTED = 'REJECTED'; class Promise { constructor(executor) { this.status = PENDING; // Macro variable, default is wait state this.value = undefined; //thenThis. Reason = undefined; //thenMethod to access so put it on thislet 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; }}; // Execute executor passing in our defined success and failure functions: pass internal resolve and reject to executor's written resolve, reject try {executor(resolve, reject); } catch(e) { console.log('catch mistakes', e); reject(e); // If an error occurs internally, pass the error down manually by calling reject}}then(onfulfilled, onrejected) {
		if (this.status === RESOLVED) {
			onfulfilled(this.value);
		}
		if (this.status === REJECTED) {
			onrejected(this.reason);
		}
	}
}
module.exports = Promise;
Copy the code

We usually use promise basically to encapsulate the logic of some request interface in a promise. When the request is successful, resolve the data out, or reject the error out, that is to say, promise must support asynchrony, then we think about how to support asynchrony?

The answer is to publish the subscriber model, and let’s see what the code does

const PENDING = 'PENDING';
const RESOLVED = 'RESOLVED';
const REJECTED = 'REJECTED'; class Promise { constructor(executor) { this.status = PENDING; // Macro variable, default is wait state this.value = undefined; //thenThis. Reason = undefined; //thenThis. OnResolvedCallbacks = []; This. OnRejectedCallbacks = []; // store successful callback functions exclusivelylet resolve = (value) => {
			if(this.status === PENDING) {this.value = value; this.status = RESOLVED; / / need to make successful methods, in turn, performs this. OnResolvedCallbacks. ForEach (fn = > fn ()); }};let reject = (reason) => {
			if(this.status === PENDING) { this.reason = reason; this.status = REJECTED; / / need to let the failure of the method in sequence enclosing onRejectedCallbacks. ForEach (fn = > fn ()); }}; // Execute executor passing in our defined success and failure functions: pass internal resolve and reject to executor's written resolve, reject try {executor(resolve, reject); } catch(e) { console.log('catch mistakes', e); reject(e); // If an error occurs internally, pass the error down manually by calling reject}}then(onfulfilled, onrejected) {
		if (this.status === RESOLVED) {
			onfulfilled(this.value);
		}
		if(this.status === REJECTED) { onrejected(this.reason); } // Handle the asynchronous caseif(this.status === PENDING) { // this.onResolvedCallbacks.push(onfulfilled); The writing can be replaced with the following wording, a layer of more package, this call for chip programming, can add your own logic to this. OnResolvedCallbacks. Push (() = > {/ / TODO... This is a big pity (this. Value); }); this.onRejectedCallbacks.push(() => { // TODO ... My logic onrejected(this.reason); }); } } } module.exports = Promise;Copy the code

Try writing some test code

let promise = new Promise((resolve, reject) => {
	setTimeout(() => {
		resolve('xxx'); }, 1000); }); // Publish subscribe should support asynchrony. A Promise canthenPromise. then((res) => {console.log()'Result of success 1', res);
}, (error) => { 
	console.log(error);
});

promise.then((res) => { 
	console.log('Results of success 2', res);
}, (error) => { 
	console.log(error);
});
Copy the code

The results of

Successful result 1 XXX Successful result 2 XXXCopy the code

At this point, we’ve done very little work but we’ve implemented the most basic and core features of Promise. Next we add the chain call, which can be tricky, but it’s easy to understand how it works if we remember the following:

  1. The then method must return a promise in order for the chain call to be guaranteed.
  2. Resolve if the result of the then callback is still a promise.
  3. Any promise must be resolved before it can go to its then method to create the next promise.
  4. When does a successful correction take place? Then returns either a normal value or a successful promise
  5. When to go for a failed callback? Return a failed promise, or throw an exception

So let’s take a look at the code and understand it


const PENDING = 'PENDING';
const RESOLVED = 'RESOLVED';
const REJECTED = 'REJECTED';

function resolvePromise(promise2, x, resolve, reject) {
	if((typeof x === 'object'&& x ! = null) || typeof x ==='function') {// It could be a promise. If it is a promise, it should bethenmethodslet then = x.then;
		if (typeof then= = ='function'// If x is a promise, the executor will execute its resolve immediately on new, and the data will be passed to its resolvethenPromise. call(x, y => {// A promise may be a promise until it is a promise. resolvePromise(promise2, y, resolve, reject); }, r => {reject(r); }); }else{a: 1,then: 1} resolve(x); }}else{ resolve(x); } } class Promise { constructor(executor) { this.status = PENDING; // Macro variable, default is wait state this.value = undefined; //thenThis. Reason = undefined; //then// Store the successful callback this.onresolvedCallbacks = []; This. OnRejectedCallbacks = [];let resolve = (value) => {
			if(this.status === PENDING) {this.value = value; this.status = RESOLVED; / / need to make a successful method performs this. OnResolvedCallbacks. ForEach (fn = > fn ()); }};let reject = (reason) => {
			if(this.status === PENDING) { this.reason = reason; this.status = REJECTED; / / need to let the failure of the method a to perform this. OnRejectedCallbacks. ForEach (fn = > fn ()); }}; Pass resolve, reject try {executor(resolve, reject); } catch (e) {console.log('catch mistakes', e); reject(e); // If an error occurs internally, pass the error down manually by calling reject}}then// Create a new promise in order to implement the chain calllet promise2 = new Promise((resolve, reject) => {
			if(this.status === RESOLVED) {// ExecutethenThis method may return a normal value, or it may return a promise. If it is a promise, you need to make the promise perform // use the macro task to set the code to execute next time so that promise2 can be fetched. And we'll talk about that latersetTimeout(() => {
					try {
						letx = onfulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } Catch (e) {// Once executedthenMethod error, go to the nextthenConsole. log(e); reject(e); }}, 0); }if (this.status === REJECTED) {
				setTimeout(() => {
					try {
						letx = onrejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); }}, 0); } // Handle the asynchronous caseif(this status = = = PENDING) {/ / at this time the executor must be asynchronous logic and enclosing onResolvedCallbacks. Push (() = > {setTimeout(() => {
						try {
							letx = onfulfilled(this.value); ResolvePromise (promise2, x, resolve, reject); } catch (e) { reject(e); }}, 0); }); this.onRejectedCallbacks.push(() => {setTimeout(() => {
						try {
							letx = onrejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); }}, 0); }); }});return promise2;
	}
}

module.exports = Promise;
Copy the code

ResolvePromise (); resolvePromise (); resolvePromise (); resolvePromise (); resolvePromise ();

  1. Promise2: a newly generated promise. Why you pass promise2 will be explained later.
  2. X: The target we’re dealing with
  3. Resolve: Resolve of the promise2. After execution, the state of the promise2 becomes successful and the result is retrieved in the successful callback of its then method.
  4. Reject: Reject of promise2, after which the state of the promise2 changes to failure and the cause of failure is retrieved in the failure callback of its then method.

At this point, the full Promise has been fulfilled, so let’s do some refinement.

Catch method

The catch method is essentially a THEN method with no successful callback, which makes sense because if it fails, you call reject, and you end up in the failed callback of the THEN method, simply changing the name of the then method.

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

Boundary determination of successful and failed callback functions

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

Make the resolvePromise compliant with the specification

What is the use of the first resolvePromise parameter promise2? It was simple enough to comply with the Promise A + specification. Let’s improve the resolvePromise

functionResolvePromise (promise2, x, resolve, reject) {// 1) Failing to refer to the same object may cause an infinite loopif (promise2 === x) {
		return reject(new TypeError('[TypeError: Chaining cycle detected for promise #<Promise>]----'));
	}
	letcalled; // There may be more than one promise implementation, but all follow the promise A + specification, we wrote the promise called, but in order to comply with the specification to add this control, because others write the promise may have multiple calls. // 2) Determine the type of x. If x is an object or a function, x may be a promise, otherwise it cannot be a promiseif((typeof x === 'object'&& x ! = null) || typeof x ==='function') {// You can make a promisethenMethod try {// becausethenMethods might be defined by getters, fetchthenTry... catch... // defineProperty(promise,'then'{/ /get() {
			// 		throw new Error();
			// 	}
			// })
			let then = x.then; 
			if (typeof then= = ='function'(()=>{}, ()=>{}, ()=>{}); Don't do this, in case the following is an error, and also to prevent multiple values //let obj = {
				// 	a: 1,
				// 	get then() {
				// 		if(this.a++ == 2) { // throw new Error(); // } // console.log(1); // } // } // obj.then; // obj.then // If x is a promise, executor will execute its resolve immediately on new, and the data will be passed to its resolvethenThen. Call (x, y => {// The current promise resolves to a promise until it resolves to a normal valueif (called) return;
					called = true; resolvePromise(promise2, y, resolve, reject); }, r => {if (called) return;
					called = true;
					reject(r);
				});
			} else {
				// {a: 1, then: 1} resolve(x); }} catch(e) {//thenAn error may invoke the promise's success or failure within the errorif (called) return;
			called = true; reject(e); }}else{ resolve(x); }}Copy the code

For 1) not being able to refer to the same object may cause an infinite loop, let’s give an example:

let promise = new Promise((resolve, reject) => {
	resolve('hello');
});
let promise2 = promise.then(() => {
	return promise2;
});
promise2.then(() => {}, (err) => {
	console.log(err);
});
Copy the code

Will report the following error

[TypeError: Chaining cycle detected for promise #<Promise>]
Copy the code

Because the then method of the promise creates the promise2, the state of which is pending, If a successful callback returns a promise, then the resolution attempts to retrieve the promise from its then method. The state of the then method is still pending, so executing promise2. Never get resolve, so you wait for yourself in an endless loop.

Resolve is also a promise

Well, there’s a situation where you say

new Promise((resolve, reject) => {
	resolve(new Promise((resolve, reject) => {
		resolve('hello');
	}));
});
Copy the code

The code we implemented above would not be able to do this, and the modifications are simple

letResolve = (value) => {// Determine valueif(value instanceof Promise) { value.then(resolve, reject); // Resolve and reject are both part of the current promise, and resolve recursively until it is a normal value. Resolve and reject are both part of the current promise, because resolve is executed after both of themreturn;
	}
	if(this.status === PENDING) {this.value = value; this.status = RESOLVED; / / need to make a successful method performs this. OnResolvedCallbacks. ForEach (fn = > fn ()); }};Copy the code

The complete code is shown below


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 {
						letx = onfulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { console.log(e); reject(e); }}, 0); }if (this.status === REJECTED) {
				setTimeout(() => {
					try {
						letx = onrejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); }}, 0); }if (this.status === PENDING) {
				this.onResolvedCallbacks.push(() => {
					setTimeout(() => {
						try {
							letx = onfulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); }}, 0); }); this.onRejectedCallbacks.push(() => {setTimeout(() => {
						try {
							letx = 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;
Copy the code

Resolve, promise.reject, Promise. All Promise. Race, Promise. Finally.