20 lines to implement a Promise

Author: dream at dawn source: gold digging

 

Preface

During the interview, there are often interviewers who ask you to implement A Promise. If you implement it by referring to the A + specification, it may not end until dark.

When it comes to Promise, the core function we first think of is asynchronous chain call. This article will take you to implement a Promise that can be called asynchronously in 20 lines of code.

The implementation of this Promise does not consider any exceptions, only the shortest code, so that readers can understand the core asynchronous chain call principle.

Code

Give the code first, it's really 20 lines.

function Promise(excutor) {
  var self = this
  self.onResolvedCallback = []
  function resolve(value) {
    setTimeout(() => {
      self.data = value
      self.onResolvedCallback.forEach(callback => callback(value))
    })
  }
  excutor(resolve.bind(self))
}
Promise.prototype.then = function(onResolved) {
  var self = this
  return new Promise(resolve => {
    self.onResolvedCallback.push(function() {
      var result = onResolved(self.data)
      if (result instanceof Promise) {
        result.then(resolve)
      } else {
        resolve(result)
      }
    })
  })
}

Core case

new Promise(resolve => {
  setTimeout(() => {
    resolve(1)
  }, 500)
})
  .then(res => {
    console.log(res)
    return new Promise(resolve => {
      setTimeout(() => {
        resolve(2)
      }, 500)
    })
  })
  .then(console.log)

This article will focus on this core case, and the performance of this code is as follows:

  1. Output 1 after 500ms

  2. Output 2 after 500ms

Realization

Constructor

First, implement the Promise constructor

function Promise(excutor) {
  var self = this
  self.onResolvedCallback = [] // Callback function set at Promise resolve

  // resolve passed to Promise handler
  // Hang a data directly on the instance here
  // Then execute the functions in the onResolvedCallback array one by one
  function resolve(value) {
    // Note that promise's then function needs to be executed asynchronously
    setTimeout(() => {
      self.data = value
      self.onResolvedCallback.forEach(callback => callback(value))
    })
  }

  // Execute the function passed in by the user
  excutor(resolve.bind(self))
}

OK, let's go back to the case

const excutor = resolve => {
  setTimeout(() => {
    resolve(1)
  }, 500)
}

new Promise(excutor)

Separately, the executor is the function passed by the user. After the resolve function is called internally, the onResolvedCallback on the promise instance will be executed once.

So far, we don't know where the function in the array of onResolvedCallback comes from, and then look down.

then

Here is the most important then implementation. Chain calls are all based on it:

Promise.prototype.then = function(onResolved) {
  // Save the context. The then of the promise call points to the promise.
  var self = this
 
  // Be sure to return a new promise
  // promise2
  return new Promise(resolve => {
    self.onResolvedCallback.push(function() {
      var result = onResolved(self.data)
      if (result instanceof Promise) {
        // The power of resolve is given to user promise
        result.then(resolve)
      } else {
        resolve(result)
      }
    })
  })
}

Back to the case

var excutor = resolve => {
  setTimeout(() => {
    resolve(1)
  }, 500)
}

var promise1 = new Promise(excutor)

promise1.then(res => {
  console.log(res)
  // user promise
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(2)
    }, 500)
  })
})

Note the naming here:

  1. We call the instance returned by the Promise constructor Promise 1,

  2. In the implementation of then, we construct a new promise return called promise 2

  3. When the user calls the then method, the user manually constructs a promise for asynchronous operation, which is called user promise

In the implementation of then, self actually points to promise1

In the executor of promise2, a function is executed immediately. It push es a function into the onResolvedCallback array of promise1,

Focus on the push function. Note that this function will not be executed until promise 1 is resolve d.

self.onResolvedCallback.push(function() {
  // onResolved corresponds to the function passed in by then
  var result = onResolved(self.data)
  // The example returns a promise 3
  if (result instanceof Promise) {
    // Then the resolution decision of promise 2 is directly handed over to user promise
    result.then(resolve)
  } else {
    resolve(result)
  }
})

If the onResolved method passed in by the user to then returns a promise, the parameter resolve obtained in the user promise actually points to the resolve of the internal promise 2,

So this can be done: after user promise is resolve d, the N2 function will continue to execute,

new Promise(resolve => {
  setTimeout(() => {
    // resolve1
    resolve(1)
  }, 500)
})
  // then1
  .then(res => {
    console.log(res)
    // user promise
    return new Promise(resolve => {
      setTimeout(() => {
        // resolve2
        resolve(2)
      }, 500)
    })
  })
  // then2
  .then(console.log)

In fact, then1 is in the callback array of promise1, so it will not be executed until resolve1 is executed

In fact, then2 is in the callback array of promise2. As we just know, resolve2 is the resolve method of promise2,

Therefore, when resolve2 is completed, then2 will execute, which implements asynchronous chain call.

Summary of key points

One core point:

  1. In a simple case, the then1 function is a synchronization function that returns a normal value. The function passed in then1 is actually put into the callback array of promise1,

// promise1
new Promise(resolve => {
    setTimeout(resolve, 1000)
})
  // then1 here the function passed in will be put into the callback array of the caller promise
.then(res => {
  console.log(res)
})

In this way, in one second, promise 1 is resolve d. Is the function in then1 executed~

  1. In complex cases, the then function returns a promise. If a promise is returned in the then function, the resolve in the promise returned actually points to the

// Call promise of then
new Promise(resolve => {
    setTimeout(resolve, 1000)
})
  // then2
.then(res => {
    // user promise
    return new Promise(resolve => {
        setTimeout(resolve, 1000)
    })
})
// then3
.then(res => {
    console.log(res)
})

then2 will return promise2 (note that it is not the user promise, but the promise 2 returned from the source code),

The function passed in by then 3 will be put into the callback array of promise 2.

Because the user returns a user promise in then2,

So the resolution power of promise 2 will be transferred to user promise,

After one second, user promise is resolve d, which means promise 2 is reoslve. Then the callback function passed in by then 3 will be found in promise 2's callback array

It's perfectly executed.

Article summary

All the above codes are in
https://github.com/sl1673495/frontend-code-fragment/blob/master/promise-easy.js

In this paper, we simply implement a promise that can be called asynchronously and chain, but the real promise is much more complex than it, involving the handling of all kinds of exceptions and boundary conditions.

The promise A + specification is worth reading for every qualified front-end development.

Hope this article can help you!

Keywords: Front-end github Fragment

Added by mdannatt on Sun, 05 Apr 2020 08:53:29 +0300