Asynchronous development


We know that Promises were created to handle asynchronous operations in JS. Before promises, callbacks were used to handle asynchronous operations. Once asynchronous operations get a little more complicated, it’s easy to get into the fabled callback hell that makes code less readable and maintainable. For example, if we wanted to implement the following asynchronous operation, the callback would be executed as follows:

let fs = require('fs');
fs.readFile('a.txt', (err,data) => {
    fs.readFile('b.txt', (err,data) => {
        fs.readFile('c.txt', (err,data) => {
            / /...
        });
    });
});
Copy the code

With promises, you can write:

let util = require('util');
let fs = require('fs');
let readFile = util.promisify(fs.readFile);

readFile('a.txt')
    .then(data= > {
        return readFile('b.txt');
    }).then(data= > {
        return readFile('c.txt');
    }).then(data= > {
        / /...
    });
Copy the code

Of course, we can also use ES7 async/await to write, which is easier:

let util = require('util');
let fs = require('fs');
let readFile = util.promisify(fs.readFile);
async function read() {
  await readFile('a.txt');
  await readFile('b.txt');
  await readFile('c.txt');
  // ...
}
read()
Copy the code

By comparison, the async/await method is the most concise and almost synchronous code. However async/await also evolved from promise and can be seen as syntactic sugar based on promise. So let’s look at how to make a promise by hand.

Basic use of Promise code


let p = new Promise((resolve, reject) = > {
  resolve(123)
})
p.then((data) = > {
  console.log(data) // We can take the value of the argument passed in resolve
}, (err) => {
  console.log('e', err)
})
Copy the code

Pass in an executor argument with new Promise()

class Promise {
  constructor(executor) {
    this.status = 'pending' // The default state is wait
    this.value = undefined
    this.reason = undefined
    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 = 'rejecte'}}try  {
      executor(resolve, reject)
    } catch (e) {
      reject(e)
    }
  }
  // The return result of the promise is taken as an argument to the outer next THEN
  // If return
  // The then method calls and returns a new promise
  then(onFufilled, onRjected) {
    if (this.status === 'resolved') {
       onFufilled(this.value)
    }
    if (this.status === 'rejected') {
      onRjected(this.reason)
    }
  }
}
Copy the code

That’s the basic promise implementation, but it’s not enough because asynchronous actions in promises are not yet supported. Such as:

let p = new Promise((resolve, reject) = > {
 // Asynchronous operations in promise
  setTimeout((a)= > {
    resolve(123)},1000)
})
p.then((data) = > {
  console.log(data) // The resolve parameter is not available
}, (err) => {
  console.log('e', err)
})
Copy the code

So when promise is pending, ondepressing and onRjected can be saved first when they are invoked by then method, and invoked when they transition to resolved or rejected. The idea of publish and subscribe is used here.

constructor(executor) {
  // ...
  this.onResolvedCallbacks = [] // Store the successful callback publish subscription
  this.onRejectedCallbacks = [] // Store the failed callback
  let resolve = value= > {
    if (this.status === 'pending') {
     // ...
      this.onResolvedCallbacks.forEach(fn= > fn()) / / release}}let reject = reason= > {
    if (this.status === 'pending') {
      // ...
      this.onRejectedCallbacks.forEach(fn= > fn())
    }
  }
 // .
}
then(onFufilled, onRjected) {
    // ...
    if (this.status === 'pending') {
      this.onResolvedCallbacks.push((a)= > { / / subscribe
        onFufilled(this.value)
      })  
      this.onRejectedCallbacks.push((a)= > { / / subscribe
        onRjected(this.reason)
      })  
    }
  }
}
Copy the code

At this point, our Promise already supports asynchronous code. Next we need to implement the chain calls to the THEN method, and here’s the hard part. Then method has the following characteristics:

  • The THEN method returns a new promise each time. If an explicit promise is returned, the promise is used as its return value (not the promise currently invoked by the THEN method). If a normal value is returned, a new promise must be manually generated and returned
  • The THEN method is invoked asynchronously
  • If an exception occurs in the THEN method, reject is called to jump to the onRejected of the next THEN
  • If the callback parameter is not returned in then(), it can be considered to skip the current THEN method and proceed to the next THEN method, i.e. value penetration
function resolvePromise(promise2, x, resolve, reject) {
  // Determine if x is a promise
  if (promise2 === x) { // x is promise2
    return reject(new TypeError('Circular reference'))}if(x ! = =null && (typeof x === 'object' || typeof x === 'function')) { // x is a promise
    let called // Avoid calling reject after resolve
    try {
      let then = x.then // take the then method of x
      if (typeof then === 'function') { // If then is a function then is a promise
        then.call(x, y => { // If y is a promise, continue recursively resolving the promise
          if (called) return
          called = true
          resolvePromise(promise2, y, resolve, reject)
        }, err => {
          if (called) return
          called = true
          reject(err)
        })
      }
    } catch(e) {
      if (called) return
      called = true
      reject(e)
    }
  } else { // Then is an ordinary object
    resolve(x)
  }
}

  then(onFufilled, onRjected) {
    // Resolve non-onfufilled onRjected issue, value penetration
    onFufilled = typeof onFufilled === 'function' ? onFufilled : y= > y
    onRjected = typeof onRjected === 'function' ? onRjected : err= > { throw err }
    let promise2 // then returns a new promise
    if (this.status === 'resolved') {
      promise2 = new Promise((resolve, reject) = > {
        // if x is a promise, it will be used as the success of promise2. If x is a normal value, it will be used as the success of promise2
        console.log(promise2)
        setTimeout((a)= > {
          try {
            let x = onFufilled(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch(e) {
            reject(e)
          }
        }, 0)})//console.log(promise2)
      // resolvePromise(promise2, x, resolve, reject)
    }
    if (this.status === 'rejected') {
      promise2 = new Promise((resolve, reject) = > {
        setTimeout((a)= > {
          try {
            let x = onRjected(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch(e) {
            reject(e)
          }
        }, 0)})}if (this.status === 'pending') { // Async code in executor
      promise2 = new Promise((resolve, reject) = > {
        this.onResolvedCallbacks.push((a)= > { / / subscribe
          setTimeout((a)= > {
            try {
              let x = onFufilled(this.value)
              resolvePromise(promise2, x, resolve, reject)
            } catch(e) {
              reject(e)
            }
          }, 0)})this.onRejectedCallbacks.push((a)= > {
          setTimeout((a)= > {
            try {
              let x = onFufilled(this.reason)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)})})}return promise2
  }

Copy the code

The resolvePromise resolves the return value of the THEN method

p.then(data= > {
    return new Promise((resolve,reject) = >{
        //resolve is passed as a Promise
        resolve(new Promise((resolve,reject) = >{
            resolve(2);
        }));
    });
})
Copy the code