Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

  • Promises/A+

  • Promise A+ Chinese translation

The use of Promise often comes up in interviews; Some interviewers dig a little deeper and ask if they know how Promise is implemented, or if they’ve read the Promise source code. Today we’ll take a look at how Promise is implemented internally to make chained calls.

What is a Promise

Before you write a generic Promise, know what it is:

  • Promise is a class
    1. Each time a new Promise is passed an executor, the executor is executed immediately (as soon as the new Promise is executed)
    2. The executor function takes two arguments, resolve,reject
    3. Pendding => resolve Reject indicates failure
    4. If once successful can not change into failure; Once you fail, you can’t succeed; You can change the state only if the current state is Pendding
    5. Each Promise has a then method
/ /! 3. Default Promise three states: Pendding,fulfiled,reject
const PENDDING = 'pendding';  / / wait for
const FULFILLED = 'fulfilled';  / / success
const REJECT = 'reject';  / / fail

class Promise {
  constructor(executor) {
    
    this.value = undefined;  // Success message
    this.reason = undefined; // Failed message
    this.status = PENDDING;  / / state values
    / /! 2. There are two parameters in the actuator: resolve and reject
    let resolve = (value) = > { 
      / /! 4. You can change the status only when the current status is Pendding
      if (this.status === PENDDING) {
        this.value = value;
        this.status = FULFILLED
      }
    }
    let reject = (reason) = > {
      if (this.status === PENDDING) {
        this.reason = reason;
        this.status = REJECT
      }
    }
    
    //* An exception may occur (throw new Error)
    try {
      / /! 1. Create a Promise executor that will be executed immediately
      executor(resolve, reject);
    } catch (e) {
      reject(e)
    }

  }
  / /! 5. Each Promise has a THEN method, which determines the current state and executes the corresponding method
  then(onFulfilled, onReject) {
    // this is a big pity. * Then there is two ways, onpity and onReject.
    if (this.status === FULFILLED) {
      onFulfilled(this.value)
    }
    if (this.status === REJECT) {
      onReject(this.reason)
    }
    if (this.status === PENDDING) {

    }
  }
}

// Export the current class commonJS definition
module.exports = Promise
Copy the code

Use:

let Promise = require('./promise')
let p = new Promise((resolve, reject) = > {
    resolve('I have money')
    // reject(' I have no money ')
    // throw new Error(' failed '); // If an exception is thrown, execution fails
})
// The callback problem is not completely resolved
p.then(data= > {  // Successful callback
  console.log('success' + data);
}, err= > {  // Failed callback
  console.log('err' + err);
})
Copy the code

Asynchronous invocation of Promise

  • The Promise class at this point is a synchronous execution function that cannot be executed at one step, and according to the specification can execute multiple THEN methods within a Promise.
let Promise = require('./promise')
let p = new Promise((resolve, reject) = > {
  setTimeout(() = > {
    resolve('I have money')
    // reject(' I have no money ')
    // throw new Error(' failed '); // If an exception is thrown, execution fails
  }, 1000);
})
// The callback problem is not completely resolved
p.then(data= > {  // Successful callback
  console.log('success' + data);
}, err= > {  // Failed callback
  console.log('err' + err);
})
p.then(data= > {  // Successful callback
  console.log('success' + data);
}, err= > {  // Failed callback
  console.log('err' + err);
})
p.then(data= > {  // Successful callback
  console.log('success' + data);
}, err= > {  // Failed callback
  console.log('err' + err);
})
Copy the code

Execution result: There is no execution result

This. OnResolvedCallbacks = []; setTimeout is executed asynchronously, the Premise is in pendding state, and the result cannot be returned. Storage space, and when the result is failure this.onRejectCallbacks = []; Store the execution functions (subscriptions) in the then method multiple times, and execute the subscription method immediately when the asynchronous function executes, that is, when the publisher publishes.

/ /! 3. Default Promise three states: Pendding,fulfiled,reject
const PENDDING = 'pendding';  / / wait for
const FULFILLED = 'fulfilled';  / / success
const REJECT = 'reject';  / / fail

class Promise {
  constructor(executor) {
    
    this.value = undefined;  // Success message
    this.reason = undefined; // Failed message
    this.status = PENDDING;  / / state values
    / /! 6. A promise can be executed multiple times then(asynchronous execution, equivalent to publish-subscribe)
    this.onResolvedCallbacks = [];
    this.onRejectCallbacks = [];
    / /! 2. There are two parameters in the actuator: resolve and reject
    let resolve = (value) = > { 
      / /! 4. You can change the status only when the current status is Pendding
      if (this.status === PENDDING) {
        this.value = value;
        this.status = FULFILLED
        this.onResolvedCallbacks.forEach(fn= > fn())  Resolve is executed after then, and if it succeeds, execute these functions in turn}}let reject = (reason) = > {
      if (this.status === PENDDING) {
        this.reason = reason;
        this.status = REJECT
        this.onRejectCallbacks.forEach(fn= > fn())
      }
    }
    
    //* An exception may occur (throw new Error)
    try {
      / /! 1. Create a Promise executor that will be executed immediately
      executor(resolve, reject);
    } catch (e) {
      reject(e)
    }

  }
  / /! 5. Each Promise has a THEN method, which determines the current state and executes the corresponding method
  then(onFulfilled, onReject) {
    // this is a big pity. * Then there is two ways, onpity and onReject.
    if (this.status === FULFILLED) {
      onFulfilled(this.value)
    }
    if (this.status === REJECT) {
      onReject(this.reason)
    }
      //* Execute publish subscribe asynchronously when the state is considered to be in wait state
    if (this.status === PENDDING) {
      this.onResolvedCallbacks.push(() = > {  / / * subscription
        //* The arrow function is used because there are other things you can do in this function
        // todo...
        onFulfilled(this.value)
      })
      this.onRejectCallbacks.push(() = > {
        onReject(this.reason)
      })
    }
  }
}

// Export the current class commonJS definition
module.exports = Promise
Copy the code

The chain call to Promise

  • The principle of
  1. If a normal value is returned, the next THEN succeeds
  2. If an error is thrown then the method fails
  3. If it’s a PROMISE, let the promise implementation take its state
  4. A new Promise is returned to implement the chain call
  • Node reads multiple files, and the native methods are controlled by the first argument of the function
let fs = require('fs');
fs.readFile('./name.txt'.'utf8'.(err,data) = > {
  if(err) {
    return console.log(err);
  }
  fs.readFile(data,'utf8'.(err,data) = > {
    if(err) {
      return console.log(err);
    }
    console.log(data); })})Copy the code
  • Into a promise

If you need to make a promise, make the callback method a promise

function readFile(. args) {
  return new Promise((resolve, reject) = >{ fs.readFile(... args,function (err, data) {
      if (err) reject(err)
      resolve(data)
    })
  })
}
// chain call
readFile('./name.txt'.'utf8').then(data= > {
 return readFile(data,'utf8')
}).then(data= > {
  console.log(data);
},err= > {
  console.log(err);
})
Copy the code
  • Handwriting realizes its principle
  1. If a normal value is returned, the next THEN succeeds
  2. If an error is thrown then the method fails
  3. If it’s a PROMISE, let the promise implementation take its state
  4. A new Promise is returned to implement the chain call

Continue with step 6 above

/ /! 3. Default Promise three states: Pendding,fulfiled,reject
const PENDDING = 'pendding';  / / wait for
const FULFILLED = 'fulfilled';  / / success
const REJECT = 'reject';  / / fail
//* Promise handler
/ /! 10. X could be a normal value, it could be a promise
const resolvePromise = (promise2, x, resolve, reject) = > {
  //* Processes the type of x to decide whether to call resolve or reject
  / /! 12 wait for yourself, will enter a loop, error, judgment
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))}/ /! 13. To determine if x is a normal value, consider yourself a promise
  if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {
    / /! 14. How to judge whether a promise is a promise by then
    try { / /! Objec. DefineProperty (); Objec. DefineProperty ()
      let then = x.then; / /! 15. See if there are any then methods
      let called; / /! 18. By default, there is no call success or failure. If there is a call, it returns to prevent multiple calls
      //* check if then is a method
      if(typeof then === 'function') { // {then:function(){}}
        / / is a promise
        // x.chen (()=>{},()=>{}
        then.call(x,y= > {  // If it is a promise, use the result of that promise
          if(called) return
          called = true;
          / /! 17. Y may also be a promise to implement recursive resolution
          resolvePromise(promise2,y,resolve,reject)  
        },r= >{
          if(called) return
          called = true;
          reject(r)
        })
      }else {
        resolve(x)// Just throw the constant}}catch (e) {
      if(called) return
      called = true;
      reject(e);  // Then throws an exception}}else {
    resolve(x) / /! 13. Either promise or normal value, return directly}}class Promise {
  constructor(executor) {
    this.value = undefined;  // Success message
    this.reason = undefined; // Failed message
    this.status = PENDDING;  / / state values
    / /! 6. A promise can be executed multiple times then(asynchronous execution, equivalent to publish-subscribe)
    this.onResolvedCallbacks = [];
    this.onRejectCallbacks = [];
    / /! 2. There are two parameters in the actuator: resolve and reject
    let resolve = (value) = > {
      / /! 4. You can change the status only when the current status is Pendding
      if (this.status === PENDDING) {
        this.value = value;
        this.status = FULFILLED
        this.onResolvedCallbacks.forEach(fn= > fn())  Resolve is executed after then, and if it succeeds, execute these functions in turn}}let reject = (reason) = > {
      if (this.status === PENDDING) {
        this.reason = reason;
        this.status = REJECT
        this.onRejectCallbacks.forEach(fn= > fn())
      }
    }

    //* An exception may occur (throw new Error)
    try {
      / /! 1. Create a Promise executor that will be executed immediately
      executor(resolve, reject);
    } catch (e) {
      reject(e)
    }

  }
  / /! 5. Each Promise has a THEN method, which determines the current state and executes the corresponding method
  then(onFulfilled, onReject) {
    // this is a big pity. * Then there is two ways, onpity and onReject.
	/ /! 19. Optional parameter. If there is no ondepressing,onReject, you can give a default parameter
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val= >val;
    onReject = typeof onReject === 'function' ? onReject : err= >{throw err};

    / /! 7. The then method should return a new promise for successive calls
    //* Execute the new Promise and then return promise2. Promise2 is undefined and a timer is required
    let promise2 = new Promise((resolve, reject) = > {
      / /! 8. The last state should be retrieved in the returned promise to determine whether the promise2 succeeds or fails
      if (this.status === FULFILLED) {
        / /! 9. Catch exceptions (if a chain call throws new Error('err'))
        / /! This is a big onFulfilled,onRejected cannot be implemented in the current context, and promise2 needs to be asynchronous to ensure the existence of promise2
        setTimeout(() = > {
          try {
            let x = onFulfilled(this.value);
            / /! 10. Handle x values externally
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e)
          }
        })
      }
      if (this.status === REJECT) {
        setTimeout(() = > {
          try {
            let x = onReject(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }
      if (this.status === PENDDING) {
        this.onResolvedCallbacks.push(() = > {  / / * subscription
          //* The arrow function is used because there are other things you can do in this function
          // todo...
          setTimeout(() = > {
            try {
              let x = onFulfilled(this.value)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        })
        this.onRejectCallbacks.push(() = > {
          setTimeout(() = > {
            try {
              let x = onReject(this.reason)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        })
      }
    })
    return promise2

  }
}

// Export the current class commonJS definition
module.exports = Promise
Copy the code

use


let Promise = require('./promise')
1. If a normal value is returned, the success of the next then * 2. If an error is thrown, then the method fails * 3. If it is a Promise, let the promise implement adopt its state * 4. A new Promise is returned to implement the chain call */
const p = new Promise((resolve,reject) = >{
    // resolve(new Promise((resolve,reject)=>{
        setTimeout(() = >{
            resolve('hello')
            // reject('hello')
        },1000)
    // }))
    
})
// let obj = {}
// Object.defineProperty(obj,'then', {
// get() {
// throw new Error(' failed ')
/ /}
// })
let promise2 = p.then(data= > {
    return new Promise((resolve,reject) = > {
        setTimeout(() = > {
            resolve('222')},1000);
    })
})
promise2.then(data= > {
    console.log(data);
},err= > {
    console.log(err);
})
Copy the code

Test written promises to see if they comply with the specification

Install the test plug-in globally

npm i promises-aplus-tests -g
Copy the code

Add the following code to the code you want to test:

/ /! 20. Test that promises comply with the specification
Promise.deferred = function(){
  let dfd = {};
  dfd.promise = new Promise((resolve,reject) = >{
    dfd.resolve = resolve;
    dfd.reject = reject
  })
  return dfd;
}
Copy the code

Start testing:

promises-aplus-tests promise.js
Copy the code