Writing in the front

Today, when I read the article, I found the request of handwritten promise. I asked myself, I depend on it, I actually have no idea. This can’t be done, you have to masturbate yourself, face sorry to their left and right hands. Ah, and to study, very tired, but ok, think about 2020, still can’t write promise, really a bit rubbish, or hurry up.


A few of promise’s requirements

  1. What is promise?
  2. What problem did Promise solve?
  3. We make a promise ourselves.

What is promise?

Promise is a solution for asynchronous operations, expressing complex traditional callback functions and asynchronous operations that listen for events in synchronous code. Nested callback functions for multi-level asynchronous operations are avoided. In short, to solve the problem of callback hell. Think about it. Does this code give you a headache? If you add a few more layers, you will have a pyramid of code, which will cause us to read the code and maintain the code.

function a() {
  function b() {
    function c() {
      function d() {}
      d();
    }
    c();
  }
  b();
}
a();
Copy the code

What problem did Promise solve?

Let’s rewrite the above code with promise. Isn’t that much better?

a().then(b).then(c).then(d)
Copy the code

Fulfill a Promise

We write it according to the promiseA+ specification. For those of you who don’t know, check it out.

Promise class

const PENDING = pending
const FULFILLED = fulfilled
const REJECTED = rejected
class Promise {
	constructor(exextuor) {
    	this.exextuor = exextuor
    	this.states = PENDING
    	this.onResolveCallbacks = []
    	this.onRejectCallbacks = []
	}
	function resolve() {}
	function reject() {}
	
	if(exextuor) {
        try {
    		exextuor(resolve, reject)
    	}catch(e) {
    		reject(e)
    	}
	}
}

Copy the code

The code above is simple: we have a resolve scheduling stack and a Reject scheduling stack, declare resolve and reject, and execute exextuor, reject if an error occurs.

Resolve and Reject functions (2.1 specification)

A promise with only one pending state (fulfilled, fulfilled, one of Rejected) may change to a fulfilled or rejected state. This is a big pity state: The state cannot be any other state, and there must be a value which cannot be changed. The rejected state cannot be in any other state. There must be a Reason and it cannot be changed.

function resolve(value) {
	if(value instanceof Promise) {
        try{
    	    value.then(resolve, reject)
    	}catch(e) {
    		reject(e)
    	}
	}
	if(this.states = PENDING) {
	    this.states = FULFILLED
        this.value = value
	    this.onResolveCallbacks.forEach(cb= > cb(this.value))
	}
}
function reject(reason) {
	if(this.states = PENDING) {
		this.states = REJECTED
		this.reason = reason
		this.onRejectCallbacks.forEach(cb= > cb(this.reason))
	}
}
Copy the code

Then functions (2.2 specification)

A promise must provide a THEN method that obtains the current or final value or reason. A promise’s then method accepts two parameters: promise. Then (onFulfilled, onRejected)

then(onFulfilled, onRejected) {
	onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value= > value
	onRejected = typeof onRejected === "function" ? onRejected : reason= > throw reason
	if(this.states = FULFILLED) {
		this.x = onFulfilled(this.value)
	}
	if(this.states = REJECTED) {
		this.x = onRejected(this.reason)
	}
	if(this.states = PENDING) {
		this.onResolveCallbacks.push((a)= > {
			this.x = onFulfilled(this.value)
		})
		this.onRejectCallbacks.push((a)= > {
			this.x = onRejected(this.reason)
		})
	}
Copy the code

A chain call to the then function

The promise’s then can be called many times in chain. If or when the promise transition is a big pity, all the ondepressing callback will be performed in the same order as when they were registered. If or when the promise transition is Rejected, all onRejected callbacks are executed in the order in which they were registered.

let primise2
if(this.states = FULFILLED) {
	return primise2 = new Promise((resole, reject) = > {
		// If there is something abnormal during the ondepressing, we will perform primise2 reject
		try {
			let x = onFulfilled(this.value)
			// Process the return value, which may be a promise
			resolvePromise(primise2, x, resole, reject)
		} catch(e) {
			reject(e)
		}
	})
}
Copy the code

Notice that the three judgments above are treated basically the same, we all have to deal with the result. ResolvePromise.

ResolvePromise (2.3 specification)

2.3.1 If the returned promise1 and X point to the same reference (circular reference), an error is thrown. 2.3.2 If X is a Promise instance, use its state

  1. If X is pending, leave it (recursively executing the Promise handler) until the pending state changes to the fulfilled or Rejected state.
  2. If or when the X state is fulfilled, resolve it, and pass in the same value as the promise1 value.
  3. If or when the X state is rejected, reject, and pass in the same value as promise1, reason
const PENDING = pending
const FULFILLED = fulfilled
const REJECTED = rejected

function resolvePromise(promise2, x, resolve, reject) {
	if(promise2 === x) {
		return reject(new TypeError('Loop in! '))}Use resolve, reject only once
	let called = false
	
	// x is a promise
	if(x instanceof Promise) {
		if(x.status === PENDING) {
			x.then((y) = >{
				// Maybe y is still a promise
				resolvePromise(promise2, y, resolve, reject)
			}, reject)
		} else {
			x.then(resolve, reject)
		}
	} else if(x ! = =null && (typeof x === 'function' || typeof x === 'object')) {
	// x is a thenable object
		try{
			// It is possible to have an exception in the region x. Chen
			let then = x.then
			if(typeof x === 'function') {
				// This is an interaction between our promise and someone else's promise
				then.call(x, (z)=>{
					// Maybe Z is still a promise
					if(called) return
					called = true
					resolvePromise(promise2, z,resolve, reject)
				}, (err)=>{
					if(called) return
					called = true
					reject(err)
				})
			}else {
				// indicates that x is a common object
				resolve(x)
			}
		}catch(e) {
			reject(e)
		}
	} else {
		// x is a normal value
		if(called) return
		called = true
		resolve(x)
	}
}
Copy the code

Catch the function

catch(onReject) {
	this.then(null, onReject)
}
Copy the code

Asynchronous invocation (3.1 specification)

3.1 The “platform code” here is the guidance engine, environment, and promise implementation code. In fact, this requirement ensures that both onFulfilled and onRejected are called asynchronously in the next event loop (a new stack). This can be done using macro tasks such as setTimeout, setImmediate, or microtasks such as MutationObsever or process.nexttick. Since the promise implementation is treated as platform code, it may itself contain a task queue or “trampoline” handler

// So we add setTimeout() to all resolve and reject calls
// We wrap the resolve function around it
// Do the same with the reject function
function resolve() {
	setTimeout((a)= >{
		// Same as above})}Copy the code

promise.all

Promise.all = function(promises) {
	return new Promise((resolve, reject) = >{
		let res = []
		let count = 0
		function done(i, result) {
			res[i] = result
			if(++count === promises.length) {
				resolve(res)
			}
		}
		for(let i = 0; i<promises.length; i++) {
			done.call(null, i,res)
		}
	}, reject)
}
Copy the code

The test results

Promise.deferred = Promise.deferred = function() {
	let defer = {}
	defer.promise = new Promise((resolve, reject) = > {
		defer.resolve = resolve
		defer.reject = reject
	})
	return defer
}
Copy the code