Promise

1. Basic concepts

Promise is an asynchronous programming solution.

Think of it as a container that holds some event (usually an asynchronous operation) that will end in the future.

Promise objects have two characteristics:

  1. The state of an object is unaffected by external influences.PromiseObjects have three states:pending.fulfilled.rejected. Instantiate aPromiseObject in the state ofpending, onlyPromiseThe result of the event stored in the container determines its subsequent state. Success forfulfilledFailure isrejectedIs not affected by external factors.
  2. Once the object state changes, it never changes again. The initial state of the object ispending, once its state changes tofulfilledorrejectedWill not change.

Advantages of Promises:

  1. Asynchronous operations can be expressed as a flow of synchronous operations, avoiding layers of nested callback functions
  2. PromiseObject provides a unified interface that makes it easier to control asynchronous operations.

Disadvantages of Promises:

  1. Can’t cancelPromiseOnce created, it is executed immediately and cannot be cancelled midway
  2. If you do not set the callback function,PromiseErrors thrown internally are not reflected externally.
  3. When inpendingYou cannot tell what stage of progress you are at (just started or almost finished)

2. Basic usage

var promiseObj = new Promise(function (resolve, reject) {
	if(...). { resolve(value) }else {
		reject(reason)
	}
})
Copy the code

When you instantiate a Promise object, you pass in a function that takes resolve and reject.

Resolve and reject do two things when they are called

  1. changePromiseObject state
  2. ensurePromiseOnce the state of an object changes, it never changes again.

ifresolveThe parameters in thevalueIs also aPromiseObject,promiseObjThe state will depend onvalueIn the state. That is, ifvalueThe status offulfilled.promiseObjThe status offulfilled. ifvalueThe status ofreject.promiseObjThe status ofreject

promiseObj.then(
	value= > {
		console.log("resolved:" + value)
	},
	reason= > {
		console.log("rejected:" + reason)
	}
)
Copy the code

The then method can take two callback functions as arguments. The first callback is called when the Promise object’s state changes to Resolved. The second callback is called when the Promise object’s state changes to Rejected. The second function is optional and does not have to be provided. Both of these functions accept as arguments a value passed from the Promise object.

3. Implementation principle

3.1 A Promise that can only implement synchronous operations (make a simple Promise first)

function Promise(execute) {
	const that = this
	this.status = "pending"       // Initialization state
	function resolve(val) {       // Do two things: 1. Make a Promise that once the state of the object changes, it will never change again
                                // 2. Change the state of the 'Promise' object
		if (that.status === "pending") {
			// Ensure that the state, once changed, will not be reversible
			that.status = "fulfilled" // Change the object state
			that.value = val          // Record the value when 'resolved'}}function reject(val) {
		if (that.status === "pending") {
      // Ensure that the state, once changed, will not be reversible
			that.status = "rejected"  // Change the object state
			that.reason = val         // Record the reason of 'rejected'
		}
	}
	execute(resolve, reject)
}
Copy the code
Promise.prototype.then = function (fulfilled, rejected) {
	if (this.status === "fulfilled") { 
		fulfilled(this.value)
	} else {
		rejected && rejected(this.reason)
	}
}
Copy the code

Because only synchronous operations are considered, when the THEN method is called, the state of the Promise object must be one of fulfilled or Rejected.

Run it:

3.2 A Promise that can implement asynchronous operations

function Promise(execute) {
	const that = this
	this.status = "pending"
	this.fulfilled = []
	this.rejected = []
	function resolve(val) {
		if (that.status === "pending") {
			that.status = "fulfilled"
			that.value = val
			that.fulfilled.forEach(element= > {
				element()
			})
		}
	}
	function reject(val) {
		if (that.status === "pending") {
			that.status = "rejected"
			that.reason = val
			that.rejected.forEach(element= > {
				element()
			})
		}
	}
	try {
		execute(resolve, reject)
	} catch (e) {
		reject(e)
	}
}
Copy the code

In contrast to promises, which can only handle synchronization, we use two arrays to hold post-asynchrony callback operations that will be performed. Iterate through the array after the state of the Promise object changes, performing an asynchronous callback.

new Promise(function (resolve) {
	throw "haha"
})
Copy the code

To prevent errors that might occur when writing container content, as shown in the example above, we use a try catch to catch the problem.

Promise.prototype.then = function (fulfilled, rejected) {
	const that = this
	if (that.status === "pending") {
		that.fulfilled.push(() = > fulfilled(that.value))
		that.rejected.push(() = > rejected(that.reason))
	}
	if (that.status === "fulfilled") {
		fulfilled(that.value)
	}
	if (that.status === "rejected") {
		rejected(that.reason)
	}
}
Copy the code

When the operation is asynchronous and the state of the Promise object is pending when the THEN method is called, the callback operation after the asynchrony is saved into the corresponding array of the instance object. After the state of the Promise object changes, the array is traversed again to perform the asynchronous callback operation.

3.3 Chainable Call (final version)

Promise.prototype.then = function (fulfilled, rejected) {
	const self = this
	fulfilled = typeof fulfilled === "function" ? fulfilled : fulfilled= > fulfilled
	rejected = typeof rejected === "function" ? rejected : rejected= > rejected
	const promise2 = new Promise(function (resolve, reject) {
		if (self.status === "pending") {
			try {
				self.fulfilled.push(() = > {
					let x = fulfilled(self.value)                 // x is the return value of the callback function
					resolvePromise(promise2, x, resolve, reject)  // Depending on the type of x value, use resolvePromise
                                                        // Perform judgment processing
				})
				self.rejected.push(() = > {
					let x = rejected(self.reason)
					resolvePromise(promise2, x, resolve, reject)
				})
			} catch (e) {  // Because the above operation may fail, use a try catch
				reject(e)
			}
		}
		if (self.status === "fulfilled") {
			try {
				let x = fulfilled(self.value)
				resolvePromise(promise2, x, resolve, reject)
			} catch (e) {
				reject(e)
			}
		}
		if (self.status === "rejected") {
			try {
				let x = rejected(self.reason)
				resolvePromise(promise2, x, resolve, reject)
			} catch (e) {
				reject(e)
			}
		}
	})

	return promise2
}
Copy the code

In contrast to the 3.2 Promise above, this version returns a new Promise object to implement the chained invocation.

In the new Promise object, x can be a normal value, a normal object, or a Promise object, so we need to decide on the x value. ResolvePromise is this handler. Here’s what the resolvePromise does:

function resolvePromise(promise2, x, resolve, reject) {
	if (promise2 === x) {
		return reject(new Error("Loop call"))}... }Copy the code

The callback function returned from the then method cannot be itself, and if it does, then the function will wait for the promise result inside, creating a callback hell with layers of state waiting.

Resolve when x is not an object.

When x is an object, resolve if it is a normal object.

If x is a Promise object, the resolvePromise function is recursively executed until the recursive y value is either a normal value or a normal object, and then resolve the value.

Why recurse:

Here’s one rule for Promise objects:

ifresolveThe parameters in thevalueIs also aPromiseObject,promiseObjThe state will depend onvalueIn the state. That is, ifvalueThe status offulfilled.promiseObjThe status offulfilled. ifvalueThe status ofreject.promiseObjThe status ofreject

After 3 seconds, output haha. Result in p2’s then method is the input parameter to P1’s resolve method. The state of P2 depends on p1, and p2’s then method is executed after P1 executes resolve.

If X is a Promise object, it might resolve a Promise object internally. Through recursive judgment, to achieve the above rules.

The complete code for the resolvePromise function is as follows:

function resolvePromise(promise2, x, resolve, reject) {
	if (promise2 === x) {
		return reject(new Error("Loop call"))}if((x ! =null && typeof x === "object") | |typeof x === "function") {
		try {
			const then = x.then
			if (typeof then === "function") {  // if x is a Promise object, it is processed recursively
				then.call(
					x,
					y= > {
						resolvePromise(promise2, y, resolve, reject)
					},
					e= > reject(e)
				)
			} else {
				resolve(x)      // x is a common object}}catch (e) {
			reject(e)
		}
	} else {
		resolve(x)          // x is a non-object}}Copy the code

The complete Promise code is as follows:

function Promise(execute) {
	const self = this
	this.status = "pending"
	this.value = undefined
	this.reason = undefined
	this.fulfilled = []
	this.rejected = []
	function resolve(val) {
		if (self.status === "pending") {
			self.status = "fulfilled"
			self.value = val
			self.fulfilled.forEach(element= > {
				element()
			})
		}
	}
	function reject(val) {
		if (self.status === "pending") {
			self.status = "rejected"
			self.reason = val
			self.rejected.forEach(element= > {
				element()
			})
		}
	}
	try {
		execute(resolve, reject)
	} catch (e) {
		reject(e)
	}
}
Promise.prototype.then = function (fulfilled, rejected) {
	const self = this
	fulfilled = typeof fulfilled === "function" ? fulfilled : fulfilled= > fulfilled
	rejected = typeof rejected === "function" ? rejected : rejected= > rejected
	const promise2 = new Promise(function (resolve, reject) {
		if (self.status === "pending") {
			try {
				self.fulfilled.push(() = > {
					let x = fulfilled(self.value)
					resolvePromise(promise2, x, resolve, reject)
				})
				self.rejected.push(() = > {
					let x = rejected(self.reason)
					resolvePromise(promise2, x, resolve, reject)
				})
			} catch (e) {
				reject(e)
			}
		}
		if (self.status === "fulfilled") {
			try {
				let x = fulfilled(self.value)
				resolvePromise(promise2, x, resolve, reject)
			} catch (e) {
				reject(e)
			}
		}
		if (self.status === "rejected") {
			try {
				let x = rejected(self.reason)
				resolvePromise(promise2, x, resolve, reject)
			} catch (e) {
				reject(e)
			}
		}
	})

	return promise2
}

function resolvePromise(promise2, x, resolve, reject) {
	if (promise2 === x) {
		return reject(new Error("Loop call"))}if((x ! =null && typeof x === "object") | |typeof x === "function") {
		try {
			const then = x.then
			if (typeof then === "function") {
				then.call(
					x,
					y= > {
						resolvePromise(promise2, y, resolve, reject)
					},
					e= > reject(e)
				)
			} else {
				resolve(x)
			}
		} catch (e) {
			reject(e)
		}
	} else {
		resolve(x)
	}
}
Copy the code