First, what are promises?

A promise is an object that may produce a single value some time in the future: Either a resolved value or a reason that it’s not resolved (e.g., a network error occurred). A promise may be in one of 3 possible states: fulfilled, rejected, or pending. Promise users can attach callbacks to handle the fulfilled value or the reason for rejection.

A Promise is an object that produces a single result at some point in the future. In layman’s terms, a Promise represents a value that we are not sure when will be returned.

A promise is an object that may produce a single value some time in the future.

Take a quick look at the history of promises

  • Promise was created in the 1980s

  • Officially named Promise in 1988

  • Promise has been widely known, but people have stuck with node.js’s call for asynchronous code that passes an error object as the first argument to a callback.

  • Dojo made the first large-scale use of promises, and the corresponding Promise/A was proposed to regulate the implementation of promises

  • JQuery started using Promises and really made them widely known

  • JQuery did not implement some of the Promise features, which led to the Promie/A+ standard

  • ES6 officially introduces Promise and is compatible with existing libraries that implement the Promise/A specification

Before we implement promises, let’s look at the specifications for promises

  1. Promise is athenableObject, which means Promise has one.then()methods
  2. A pending Promise can enter the fulfilled and Rejected states
  3. Once the promise enters the fulfilled or rejected state, it cannot change its state again
  4. Once a promise changes its state, it has a refill value (which may also be undefined)

Start fulfilling a Promise

First, let’s take a look at the most common asynchronous code:

// Asynchronous method definition
var basicAsyncFunc = function(callback) {
  setTimeout(function() {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) {
      callback(null, randomNumber)
    } else {
      callback(new Error('bad luck... '}})),1000)}// Asynchronous method invocation
basicAsyncFunc((err, result) = > {
  if (err) {
    console.log(`the reason fail is: ${err}`)
    return
  }
  console.log(`success get result: ${result}`)})Copy the code

According to the normative definition of Promise, the ideal way to invoke Promise is:

// Asynchronous method definition in the form of Promise
var promiseAsyncFunc = function() {}

// Asynchronous method invocation in the form of Promise
promiseAsyncFunc.then(
  data= > {
    console.log(`success get result: ${data}`)
  },
  err => {
    console.log(`the reason fail is: ${err}`)})Copy the code

With this ideal invocation, let’s write the first version of the code.

First version Promise: You can save callback methods

// Asynchronous method definition in the form of Promise
var promiseAsyncFunc = function() {
  var fulfillCallback
  var rejectCallback

  setTimeout((a)= > {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) fulfillCallback(randomNumber)
    else rejectCallback(randomNumber)
  }, 1000)
  return {
    then: function(_fulfillCallback, _rejectCallback) {
      fulfillCallback = _fulfillCallback
      rejectCallback = _rejectCallback
    }
  }
}

// Asynchronous method invocation in the form of Promise
promiseAsyncFunc().then(fulfillCallback, rejectCallback)
Copy the code

The idea is to save the fullfill and reject callback functions in the.then() method and call them in the asynchronous method. Because this is fulfilling-loop, the callback of fulfilling-loop () must be assigned by the end of the asynchronous call as promiseAsyncFunc().

Second version Promise: real constructors

The asynchronous logic code and the Promise code are currently mixed together in our implementation of promises, so let’s separate them:

var promiseAsyncFunc = function() {
  var fulfillCallback
  var rejectCallback

  return {
    fulfill: function(value) {
      if (fulfillCallback && typeof fulfillCallback === 'function') {
        fulfillCallback(value)
      }
    },
    reject: function(err) {
      if (rejectCallback && typeof rejectCallback === 'function') {
        rejectCallback(err)
      }
    },
    then: function(_fulfillCallback, _rejectCallback) {
      fulfillCallback = _fulfillCallback
      rejectCallback = _rejectCallback
    }
  }
}

let ownPromise = function(asyncCall) {
  let promise = promiseAsyncFunc()
  asyncCall(promise.fulfill, promise.reject)
  return promise
}

// Asynchronous method invocation in the form of Promise
ownPromise(function(fulfill, reject) {
  setTimeout((a)= > {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) fulfill(randomNumber)
    else reject(randomNumber)
  }, 1000)})Copy the code

We define a new method ownPromise() to create promises and expose the FULFILL and reject interfaces in promiseAsyncFunc() for asynchronous code to invoke.

There is a problem here. After calling ownPromise() and getting a promise instance, we can call fulfill(), reject() directly, whereas theoretically we should only expose the promise’s then() method. So we use closures to hide these two methods:

var promiseAsyncFunc = function() {
  var fulfillCallback
  var rejectCallback

  return {
    fulfill: function(value) {
      if (fulfillCallback && typeof fulfillCallback === 'function') {
        fulfillCallback(value)
      }
    },
    reject: function(err) {
      if (rejectCallback && typeof rejectCallback === 'function') {
        rejectCallback(err)
      }
    },
    promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        fulfillCallback = _fulfillCallback
        rejectCallback = _rejectCallback
      }
    }
  }
}

let ownPromise = function(asyncCall) {
  let defer = promiseAsyncFunc()
  asyncCall(defer.fulfill, defer.reject)
  return defer.promise
}

// Asynchronous method invocation in the form of Promise
ownPromise(function(fulfill, reject) {
  setTimeout((a)= > {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) fulfill(randomNumber)
    else reject(randomNumber)
  }, 1000)})Copy the code

Version 3 Promise: Support for state management

In order to implement the specification for Promise state changes, we need to add state management for the Promise. This step is relatively simple. Let’s look at the code:

const PENDING = Symbol('pending')
const FULFILLED = Symbol('fulfilled')
const REJECTED = Symbol('rejected')

// Asynchronous method definition in the form of Promise
var promiseAsyncFunc = function() {
  var status = PENDING
  var fulfillCallback
  var rejectCallback

  return {
    fulfill: function(value) {
      if(status ! == PENDING)return
      if (typeof fulfillCallback === 'function') {
        fulfillCallback(value)
        status = FULFILLED
      }
    },
    reject(error) {
      if(status ! == PENDING)return
      if (typeof rejectCallback === 'function') {
        rejectCallback(error)
        status = REJECTED
      }
    },
    promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        fulfillCallback = _fulfillCallback
        rejectCallback = _rejectCallback
      }
    }
  }
}

let ownPromise = function(asyncCall) {
  let defer = promiseAsyncFunc()
  asyncCall(defer.fulfill, defer.reject)
  return defer.promise
}

// Asynchronous method invocation in the form of Promise
ownPromise(function(fulfill, reject) {
  setTimeout((a)= > {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) fulfill(randomNumber)
    else reject(randomNumber)
  }, 1000)
}).then(data= > console.log(data), err => console.log(err))
Copy the code

In this code, we use Symbol to represent the state constant, students who do not know Symbol can see here

In order to judge the state of Promise, we added two methods fulfill and reject. And determine the current state of the promise. Return if it is not a pending state (because the Promise state can only change once).

Now our promise implements the specification for state control:

  • Only one state change is allowed
  • Can only be fulfilled from pending => depressing or pending => Rejected

But there was a problem with our Promise: the Promise value was not saved. If a promise is called to the.then() method after the asynchronous call completes, we cannot pass the result of the asynchronous call to the callback function. To do this we need to add a value field to the Promise:

Version 4 Promise: Saves the result of an asynchronous call

We added a value field to the promise to hold the result of the promise’s execution.

// Asynchronous method definition in the form of Promise
var promiseAsyncFunc = function() {
  var status = PENDING
  var fulfillCallback
  var rejectCallback
  var value

  return {
    fulfill: function(_value) {
      if(status ! == PENDING)return
      value = _value
      status = FULFILLED
      if (typeof fulfillCallback === 'function') {
        fulfillCallback(value)
      }
    },
    reject(error) {
      if(status ! == PENDING)return
      value = error
      status = REJECTED
      if (typeof rejectCallback === 'function') {
        rejectCallback(error)
      }
    },
    promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        fulfillCallback = _fulfillCallback
        rejectCallback = _rejectCallback
      }
    }
  }
}
Copy the code

Here we find another problem: if a Promise is already in the FULFILL or reject state. When we call the then() method, the incoming callback method will never be called (because status is not pending).

So we need to judge its state in the then() method:

// Asynchronous method definition in the form of Promise
var promiseAsyncFunc = function() {
  var status = PENDING
  var fulfillCallback
  var rejectCallback
  var value

  return {
    fulfill: function(_value) {
      if(status ! == PENDING)return
      value = _value
      status = FULFILLED
      if (typeof fulfillCallback === 'function') {
        fulfillCallback(value)
      }
    },
    reject(error) {
      if(status ! == PENDING)return
      value = error
      status = REJECTED
      if (typeof rejectCallback === 'function') {
        rejectCallback(error)
      }
    },
    promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        if (status === REJECTED) {
          _rejectCallback(value)
          return
        }
        if (status === FULFILLED) {
          _fulfillCallback(value)
          return
        }
        fulfillCallback = _fulfillCallback
        rejectCallback = _rejectCallback
      }
    }
  }
}
Copy the code

Version 5 Promise: Support for chained calls

To support chained calls, the.then() method must return thenable. (According to the Promise/A+ specification, the.then() method must return A new Promise.)

To do this we add a utility method makeThenable(). If the value passed in already has a then() method, value is returned. Otherwise return an object with a then() method. In the object’s then() method, we call different callback methods to generate a new value, depending on the state of the promise.

function makeThenable(The value of the status){
  if(value && typeof value.then === 'function') {return value
  }
  if(status === FULFILLED){
    return {
      then: function(fulfillCallback, rejectCallback){
        return makeThenable(fulfillCallback(value), FULFILLED)
      }
    }
  }
  if(status === REJECTED) {
    return {
      then: function(fulfillCallback, rejectCallback){
        return makeThenable(rejectCallback(value), FULFILLED)
      }
    }
  }
}
Copy the code

With the makeThenable() method above, we can set value to thenable in the fulfill(), reject() promise reply:

var promiseAsyncFunc = function() {
  var status = PENDING
  var fulfillCallback
  var rejectCallback
  var value

  return {
    fulfill: function(_value) {
      if(status ! == PENDING)return
      value = makeThenable(_value, FULFILLED) // Ensure that the value of the current promise is thenable
      status = FULFILLED
      if (typeof fulfillCallback === 'function') {
        value.then(fulfillCallback)
      }
    },
    reject(error) {
      if(status ! == PENDING)returnValue = makeThenable(error, REJECTED),,// Ensure that the current value is thenable
      status = REJECTED
      if (typeof rejectCallback === 'function') {
        value.then(null, rejectCallback)
      }
    },
    promise: {
      then: function(){}}}}Copy the code

Let’s look at the then() method. To return a new promise, we first have to create a new promise. Second, the fullfill() or reject() method of the new promise should be called when the current promise takes up () or reject(). So we wrap fulfullCallback and rejectCallback when we assign them to the current Promise. The code is as follows:

    promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        let newPromiseAsyncFunc = promiseAsyncFunc()
        let fulfillFunc = function(value) {
          newPromiseAsyncFunc.fulfill(_fulfillCallback(value))
        }
        let rejectFunc = function(err) {
          newPromiseAsyncFunc.fulfill(_rejectCallback(err))
        }
        if (status === PENDING) {
          fulfillCallback = fulfillFunc
          rejectCallback = rejectFunc
        } else {
          value.then(fulfillFunc, rejectFunc)
        }
        return newPromiseAsyncFunc.promise
      }
    }
Copy the code

Thus, we have a promise that can be called in a chain. Let’s test it out:

const PENDING = Symbol('pending')
const FULFILLED = Symbol('fulfilled')
const REJECTED = Symbol('rejected')

function makeThenable(value, status) {
  if (value && typeof value.then === 'function') {
    return value
  }
  if (status === FULFILLED) {
    return {
      then: function(fulfillCallback, rejectCallback) {
        return makeThenable(fulfillCallback(value), FULFILLED)
      }
    }
  }
  if (status === REJECTED) {
    return {
      then: function(fulfillCallback, rejectCallback) {
        return makeThenable(rejectCallback(value), FULFILLED)
      }
    }
  }
}

// Asynchronous method definition in the form of Promise
var promiseAsyncFunc = function() {
  var status = PENDING
  var fulfillCallback
  var rejectCallback
  var value

  return {
    fulfill: function(_value) {
      if(status ! == PENDING)return
      value = makeThenable(_value, FULFILLED)
      status = FULFILLED
      if (typeof fulfillCallback === 'function') {
        value.then(fulfillCallback)
      }
    },
    reject(error) {
      if(status ! == PENDING)return
      value = makeThenable(error, REJECTED)
      status = REJECTED
      if (typeof rejectCallback === 'function') {
        value.then(null, rejectCallback)
      }
    },
    promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        let newPromiseAsyncFunc = promiseAsyncFunc()
        let fulfillFunc = function(value) {
          newPromiseAsyncFunc.fulfill(_fulfillCallback(value))
        }
        let rejectFunc = function(err) {
          newPromiseAsyncFunc.fulfill(_rejectCallback(err))
        }
        if (status === PENDING) {
          fulfillCallback = fulfillFunc
          rejectCallback = rejectFunc
        } else {
          value.then(fulfillFunc, rejectFunc)
        }
        return newPromiseAsyncFunc.promise
      }
    }
  }
}

let ownPromise = function(asyncCall) {
  let defer = promiseAsyncFunc()
  asyncCall(defer.fulfill, defer.reject)
  return defer.promise
}

let testChainedPromise = ownPromise(function(fulfill, reject) {
  setTimeout((a)= > {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) fulfill(randomNumber)
    else reject(randomNumber)
  }, 1000)
})
  .then(
    data= > {
      console.log(data)
      return 'return value in then1 fulfill'
    },
    err => {
      console.log(err)
      return 'return value in then1 reject'
    }
  )
  .then(
    data= > {
      console.log(data)
      return 'return value in then2 fulfill'
    },
    err => {
      console.log(err)
      return 'return value in then2 reject'
    }
  )
  .then(
    data= > {
      console.log(data)
    },
    err => {
      console.log(err)
    }
  )

/** console output: 0.9931984611850693 return value in then1 fulfill return value in then2 fulfill */
Copy the code

Version 6 Promise: Error Handling

Here we only handle errors thrown in asynchronous calls and fulfill callbacks.

We start with the asynchronous call part, where we try catch it, call reject when an exception occurs, and pass in the exception as a parameter.

let ownPromise = function(asyncCall) {
  let defer = promiseAsyncFunc()
  try {
    asyncCall(defer.fulfill, defer.reject)
  } catch (e) {
    defer.reject(e)
  }
  return defer.promise
}
Copy the code

Then there are abnormalities that may occur in FULFILL. We catch exceptions of fulfillCallback(value) and pass them to rejectCallback.

function makeThenable(value, status) {
  if (value && typeof value.then === 'function') {
    return value
  }
  if (status === FULFILLED) {
    return {
      then: function(fulfillCallback, rejectCallback) {
        try {
          let newValue = fulfillCallback(value)
          return makeThenable(newValue, FULFILLED)
        } catch (e) {
          return makeThenable(rejectCallback(e), FULFILLED)
        }
      }
    }
  }
  if (status === REJECTED) {
    return {
      then: function(fulfillCallback, rejectCallback) {
        return makeThenable(rejectCallback(value), FULFILLED)
      }
    }
  }
}
Copy the code

Finally, let’s test the complete code:

const PENDING = Symbol('pending')
const FULFILLED = Symbol('fulfilled')
const REJECTED = Symbol('rejected')

function makeThenable(value, status) {
  if (value && typeof value.then === 'function') {
    return value
  }
  if (status === FULFILLED) {
    return {
      then: function(fulfillCallback, rejectCallback) {
        try {
          let newValue = fulfillCallback(value)
          return makeThenable(newValue, FULFILLED)
        } catch (e) {
          return makeThenable(rejectCallback(e), FULFILLED)
        }
      }
    }
  }
  if (status === REJECTED) {
    return {
      then: function(fulfillCallback, rejectCallback) {
        return makeThenable(rejectCallback(value), FULFILLED)
      }
    }
  }
}

// Asynchronous method definition in the form of Promise
var promiseAsyncFunc = function() {
  var status = PENDING
  var fulfillCallback
  var rejectCallback
  var value

  return {
    fulfill: function(_value) {
      if(status ! == PENDING)return
      value = makeThenable(_value, FULFILLED)
      status = FULFILLED
      if (typeof fulfillCallback === 'function') {
        value.then(fulfillCallback)
      }
    },
    reject(error) {
      if(status ! == PENDING)return
      value = makeThenable(error, REJECTED)
      if (typeof rejectCallback === 'function') {
        value.then(null, rejectCallback)
      }
      status = REJECTED
    },
    promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        let newPromiseAsyncFunc = promiseAsyncFunc()
        let fulfillFunc = function(value) {
          newPromiseAsyncFunc.fulfill(_fulfillCallback(value))
        }
        let rejectFunc = function(err) {
          newPromiseAsyncFunc.fulfill(_rejectCallback(err))
        }
        if (status === PENDING) {
          fulfillCallback = fulfillFunc
          rejectCallback = rejectFunc
        } else {
          value.then(fulfillFunc, rejectFunc)
        }
        return newPromiseAsyncFunc.promise
      }
    }
  }
}

let ownPromise = function(asyncCall) {
  let defer = promiseAsyncFunc()
  try {
    asyncCall(defer.fulfill, defer.reject)
  } catch (e) {
    defer.reject(e)
  }
  return defer.promise
}

let testChainedPromise = ownPromise(function(fulfill, reject) {
  throw Error('here is an error in asyncCall')
  setTimeout((a)= > {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) fulfill(randomNumber)
    else reject(randomNumber)
  }, 1000)
})
  .then(
    data= > {
      console.log(data)
      return 'return value in then1 fulfill'
    },
    err => {
      console.log(err.message)
      return 'return value in then1 reject'
    }
  )
  .then(
    data= > {
      console.log(data)
      throw Error('here is an error in fulfill1')
      return 'return value in then2 fulfill'
    },
    err => {
      console.log(err.message)
      return 'return value in then2 reject'
    }
  )
  .then(
    data= > {
      console.log(data)
    },
    err => {
      console.log(err.message)
    }
  )


// console out:
Error: here is an error in asyncCall
return value in then1 reject
Error: here is an error in fulfill1
return value in then2 reject
Copy the code

conclusion

The above is our Promise for A simple implementation, implementation ideas mainly refer to the Q-A Promise library for javascript. The Promise feature of this implementation is rudimentary and only partially implements the API/specification. Any comments and suggestions are welcome in the comments section;)

Further reading && quotes

Explanation of the use of Promise and error Handle:

Medium.com/javascript-…

One of the Promise implementation libraries: Q

Github.com/kriskowal/q…

Want to learn more about front-end and data visualization?

Here is my github address for front-end, D3.js, data visualization, and welcome to Star & Fork: Tada:

ssthouse-blog

If you think this article is good, check out the link below:)

Making the home page

Zhihu column

The Denver nuggets

Want to contact me directly?

Email address: [email protected]

Welcome to follow my official account: