Daily study notes, including ES6, Promise, Node.js, Webpack, HTTP principles, Vue family buckets, and possibly more Typescript, Vue3, common interview questions, etc.

Promise

References Promise | MDN

Why Promise occurs: Handling multiple concurrent requests solves the problem of callback hell with chained calls.

A Promise has three states: resolve, reject, and Pending.

First, Promise is a class that needs to be instantiated with the keyword new.

A Promise accepts an executor function as an executor, which executes immediately. It also accepts two parameters as a callback for success and failure.

When we do not execute a success or failure callback, the current Promise state remains in the wait state. The Promise class returns a Promise class for the next call.

let promise = new Promise((resolve, reject) = >{})
console.log(promise) // Promise {<pending>}
Copy the code

The return value of the Promise instance will determine whether the current success or failure status is returned based on the function called, and will return the passed arguments. When the function is called, undefined is returned if no arguments are passed.

// Nothing
let promise = new Promise((resolve, reject) = > {
    resolve()
})
console.log(promise); // Promise { undefined }

// Success status
let promise = new Promise((resolve, reject) = > {
    resolve('success')})console.log(promise); // Promise { 'success' }

// Failed
let promise = new Promise((resolve, reject) = > {
    reject('failed')})console.log(promise); // Promise { <rejected> 'failed' }
Copy the code

On each instance of a Promise, there is a.then method that prints the result passed in by the previous instance. Once the current instance state has been changed, it cannot be changed.

let promise = new Promise((resolve, reject) = > {
    resolve('success')
}).then((result) = > {
    console.log(result); // success
}, (error) = > {
    console.log(error);
})
Copy the code

In this case, we can summarize several characteristics of promises.

The characteristics of

  1. PromiseIs a class, and there are no compatibility issues.
  2. PromiseIt passes in a function (executor) as an executor, this executor executes immediately.
  3. executorProvides two functions (resolvereject) is used to describe the currentPromiseWhile the current instance has three states,The successful stateThe failure stateWait state, the current instance defaults toWait state. If the callresolveThen the state becomesThe successful state, the callrejectOr an exception occursThe failure state
  4. PromiseOnce the state has changed, it cannot be changed.
  5. eachPromiseThere’s one for each instance.thenMethods.

We can write a set of promises by hand based on a few characteristics of promises.

Handwriting Promises/A+ specifications

Document specification Promises/A+

Note: The code content is continuous, please watch in order. thank you

Promise’s base features

According to the above characteristics, we can simply achieve the Promise effect.

const PEDDING = 'PEDDING'; // Wait state
const FULFILLED = 'FULFILLED'; // Success status
const REJECTED = 'REJECTED'; // Failed
class Promise {
  constructor(executor) { 
    this.status = PEDDING; // Default state
    this.result = undefined; // Successful callback
    this.reason = undefined; // Failed callback
    const resolve = (result) = > { // The resolve function succeeds
      if (this.status === PEDDING) {
        this.status = FULFILLED; // Modify the status
        this.result = result; // Add a callback}}const reject = (reason) = > { // Reject fails
      if (this.status === PEDDING) {
        this.status = REJECTED; // Modify the status
        this.reason = reason; // Add a callback}}try {
      executor(resolve, reject)
    } catch (error) {
      this.reason = error; }}then(onFulfilled, onRejected) {
    if (this.status === FULFILLED) { // The method to call on success
      onFulfilled(this.result)
    }
    if (this.status === REJECTED) { // Method called on failure
      onRejected(this.reason)
    }
  }
}
module.exports = Promise
Copy the code

By referring to the Promise A+ specification, we can simply implement A simple implementation version of the Promise class.

Implement the Promise’s asynchronous functionality

To make promises async, we need to make it clear that promises are async only when they trigger.then methods (resolve and reject). So we use this idea.

When the user calls the.then method, the Promise may be in a wait state and we need to temporarily save it. Later calls to resolve and Reject trigger the corresponding onFulfilled and onRejected

Based on the description above, we can capture the two keywords staging and triggering, so we can use the publish-and-subscribe design pattern to achieve this functionality.

// ...
class Promise {
  constructor(executor) {
    // ...
    this.onResolveCallbacks = []; // To store successful callbacks
    this.onRejectCallbacks = []; // To store the failed callback
    const resolve = (result) = > {
      if (this.status === PEDDING) {
        // ...
        this.onResolveCallbacks.forEach(fn= > fn())
      }
    }
    const reject = (reason) = > {
      if (this.status === PEDDING) {
      // ...
        this.onRejectCallbacks.forEach(fn= > fn())
      }
    }
    // ...
  }
  then(onFulfilled, onRejected) {
    if (this.status === PEDDING) {
      this.onResolveCallbacks.push(() = > {
        onFulfilled(this.result)
      })
      this.onRejectCallbacks.push(() = > {
        onRejected(this.reason)
      })
    }
    // ...}}module.exports = Promise
Copy the code

Create two arrays to store the callback functions. First store the functions that need to be executed into the array. When the asynchronous execution is complete, the functions stored in the array are executed in turn.

Promise chain calls

First of all, what problems did Promise solve?

  • Handle multiple concurrent requests
  • Chained calls solve the problem of callback hell

What is callback hell? Callback hell is when we normally deal with business code, the API parameters of the next interface need to use the parameters of the last interface. Multiple levels of nesting can occur in the code, making it difficult to read.

Here we need to use a chained call to the Promise, which is a loop call to the.then method, which returns a new Promise when called.

Let’s first encapsulate a Promise asynchronous function

let fs = require('fs');
function readFile(path, encoding) {
  return new Promise((resolve, reject) = > {
    fs.readFile(path, encoding, (err, data) = > {
      if (err) reject(err)
      resolve(data)
    })
  })
}
Copy the code

Now we need to be aware of several situations in which chained calls occur.

  1. If the.then method returns an ordinary value (not a Promise), it will be the successful result of the next outer.then method.

    readFile('./a.txt'.'utf8').then((result) = > {
      return 1;
    }, (err) = > {
      console.log(err);
    }).then((result) = > {
      console.log(result); / / 1
    }, (err) = > {
      console.log(err);
    })
    Copy the code
  2. If the.then method fails, it will go to the outer layer of the next failure result of the.then method.

    readFile('./a.txt'.'utf8').then((result) = > {
      throw new Error('error')},(err) = > {
      console.log(err);
    }).then((result) = > {
      console.log(result);
    }, (err) = > {
      console.log(err); // Error: error
    })
    Copy the code

    (Note: Execution error requiredthrow new Error()If used directlyreturn new Error(), belong to return oneThe Error object, will execute the next successful result)

  3. The successful result of the.then method is executed the next time, as long as normal values are returned, regardless of whether the last execution of the.then method succeeded or failed.

    If the path is incorrectly entered, the Promise defaults to the error result of the first layer. Then method and returns undefined. Then the execution result of the next layer is the success result and the value is undefined

    // The entered path is incorrect
    readFile('./a.txt1'.'utf8').then((result) = > {
      console.log(result)
    }, (err) = > {
      // Return undefined here
      console.log(err); // Error cause
    }).then((result) = > {
      console.log(result); // undefined
    }, (err) = > {
      console.log(err);
    })
    Copy the code
  4. If the.then method returns a Promise object, the result of the Promise is treated as a success or failure * (success or failure is passed in).

    readFile(`${bathPath}a.txt`.'utf8').then((result) = > {
        return readFile(`${bathPath}${result}`.'utf8')},(err) = > {
        console.log('err1', err);
    }).then((result) = > {
        console.log('success2', result); // success2 b
    }, (err) = > {
        console.log('err2', err); // error
    })
    Copy the code

Conclusion: If the return is a normal value (not a Promise), it is passed to the next time.thenThe success of the method. If it returns a failed Promise or throws an exception, it is passed to the next time.thenMethod failure.

Hand-written implementation promise chained calls

Depending on the characteristics and circumstances described above, we will return a new Promise instance every time after the.then method is called. So we can modify the.then method we wrote earlier.

Let’s first deal with the normal value (not the Promise) case.

(Note: here we separate out an X for subsequent processing)

// Rewrite the.then() method
then(onFulfilled, onRejected) {
	let promise = new Promise((resolve, reject) = > { // Return a Promise instance
		if (this.status === FULFILLED) {
			try {
        let x = onFulfilled(this.result)
				resolve(x);
			} catch (e) {
				reject(e)
			}
		}
		if (this.status === REJECTED) {
			try {
        let x = onRejected(this.reason)
				resolve(x)
			} catch (e) {
				reject(e)
			}
		}
		if (this.status === PEDDING) {
			this.onResolveCallbacks.push(() = > {
				try {
					let x = onFulfilled(this.result)
					resolve(x);
				} catch (e) {
					reject(e)
				}
			})
			this.onRejectCallbacks.push(() = > {
				try {
					let x = onRejected(this.reason)
					resolve(x)
				} catch (e) {
					reject(e)
				}
			})
		}
	})
	return promise
}
Copy the code

This idea is used to modify the previous method so that we can work with ordinary values.

We can modify the above case for ordinary values to handle more cases. To do this we need to encapsulate a resolvePromise() function to do the processing.

ResolvePromise () takes four arguments: the current instance promise, the result X, the success callback resolve, and the failure callback reject.

In order to pass the current strength promise as an argument, we need to first encapsulate it with the asynchronous method setTimeout (or any other method).

then(onFulfilled, onRejected) {
	let promise = new Promise((resolve, reject) = > { // Return a Promise instance
		if (this.status === FULFILLED) {
      setTimeout(() = > {
        try {
          let x = onFulfilled(this.result)
          // This is where the package is processed
          resolvePromise(promise, x, resolve, reject);
        } catch (e) {
          reject(e)
        }
      }
		}
    / /... Do the same in the following code
	})
	return promise
}
Copy the code

Now that we can read the Promise instance, let’s implement the resolvePromise() function.

function resolvePromise(promise, x, resolve, reject) {
	if (promise === x) {
		return reject(new TypeError('wrong'))}// Promise compatibility
  if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {
    try {
      let then = x.then // When implemented with defineProperty, there may be an exception in the value
      if (typeof then === 'function') {
        then.call(x, y= > {
        	resolve(y)
      	}, r= > {
      		reject(r)
      	})
      } else {
        resolve(x)
      }
    } catch (e) {
    	reject(e)
    }
  } else {
    / / common values
  	resolve(x)
  }
}
Copy the code

(Note: At work, we may call promises that are encapsulated by others, and there may be problems with them. So we need to do one more step, which is to put a lock in the code to ensure that the code is strict.

function resolvePromise(promise, x, resolve, reject) {
	// ...
  if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {
    let called = false; // Define a parameter
    try {
      let then = x.then // When implemented with defineProperty, there may be an exception in the value
      if (typeof then === 'function') {
        then.call(x, y= > {
          // Make an exception judgment here
          if (called) return
					called = true
        	resolve(y)
      	}, r= > {
          if (called) return
					called = true
      		reject(r)
      	})
      } else {
        resolve(x)
      }
    } catch (e) {
      if (called) return
      called = true
    	reject(e)
    }
  } else {
    // ...}}Copy the code

Thus we implement a chained call to a Promise.

Special case handling

Nested Promise

There may be another case where we pass a Promise instance in the resolve of the.then method. How do we handle this case?

The following situation

let promise = new Promise((resolve, reject) = > {
	resolve(1)
}).then(() = > {
  return new Promise((resolve, reject) = > {
    setTimeout(() = > {
    	resolve(new Promise((resolve, reject) = > {
        setTimeout(() = > {
        	resolve(200)},1000); }})),1000);
  })
})

promise.then(result= > {
	console.log(result);
}, err= > {
	console.log(err);
})
Copy the code

In this particular case, we need to continue to modify the previous resolvePromise() function.

function resolvePromise(promise, x, resolve, reject) {
	// ...
  if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {
		// ...
    try {
      // ...
      if (typeof then === 'function') {
        then.call(x, y= > {
          // ...
        	// Keep parsing until it is not a Promise
					resolvePromise(promise, y, resolve, reject)
      	}, r= > {
          // ...})}else {
        resolve(x)
      }
    } catch (e) {
      // ...}}else {
    // ...}}Copy the code

The key is to recurse the call until it has a normal value.

Parameters through

The following happens when we call the.then method

new Promise((resolve, reject) = > {
	resolve(100)
  // reject('err')
}).then().then().then().then(result= > {
	console.log(result); / / 100
}, err= > {
	console.log(err); // If passed, output err
})
Copy the code

With no parameters passed in, the result is passed until output.

In this case of parameter penetration, we also need to make changes in the code.

then(onFulfilled, onRejected) {
  // This will be fulfilled
	onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v= > v;
  onRejected = typeof onRejected === 'function' ? onRejected : err= > {throw err}; // The error will only be output if thrown, so throw
  // ...
}
Copy the code

Promise to test

We can test our own encapsulated promises using the test package Promises -aplus-tests.

Execute the following code in the Promise instance directory

npm install promises-aplus-tests -g
promises-aplus-tests ./promise.js
Copy the code

He will automatically check whether the Promise we encapsulate meets the Promise A+ specification.

Add a delay object under the Promise file we encapsulated.

class Promise {
  / /... Self encapsulated Promise
}

// The code that needs to be tested
Promise.deferred = function () {
	let dfd = {};
	dfd.promise = new Promise((resolve, reject) = > {
		dfd.resolve = resolve
		dfd.reject = reject
	})
	return dfd
}

module.exports = Promise
Copy the code

(Note: Catch, all, etc., are not included in the Promise specification)

After the detection, we can see the output results, according to which we can know whether the Promise encapsulated can run normally.

At this point, we have encapsulated a Promise.

Delay object

To help us reduce one application, which is not very widely used. It’s kind of like an agency.

We can encapsulate our initial readFile read operation.

function readFile(path, encoding) {
  let dfd = Promise.deferred();
	fs.readFile(path, encoding, (err, data) = > {
		if (err) return dfd.reject(err)
		dfd.resolve(data)
	})
  return dfd.promise;
}
Copy the code

This article is created by Mo Xiaoshang, if there are any problems and flaws in the article, you are welcome to correct and communicate. You can also follow my personal site, blog garden and nuggets, and I will upload my posts to these platforms as soon as they are produced. Finally, thank you for your support!