Here’s what you’ll learn after reading this article:

  1. Promise a brief history of
  2. Key concepts of Promise
  3. You can write standards-compliant promises by hand
  4. You can understand the sequence of macro/micro tasks

The foreword 0.

Why write this article?

JavaScript is an asynchronous language, so promises are important.

And I read some of the articles, the quality is uneven.

So systematically sorted out some information, and then output an article, that is, to help others, but also let everyone give me pick problems, to avoid their mistakes and DO not know.

Due to the limited ability, the article may be wrong, hope the majority of netizens correct.

1. Promise

Promise is not a new concept, having been around the community since 2011 as a solution to the famous callback hell problem.

This concept became popular after JQuery Deferred Objects appeared. And in 2012, Promise was proposed as A specification: Promise/A+.

Before becoming ES6 standard, there were many promising libraries in the community, such as Bluebird, Q, WHEN, and so on.

Key concepts of Promise

The Promise object is used for deferred and asynchronous computations. A Promise represents an operation that hasn’t Completed yet, but is expected in the future. “- MDN Promise Reference

Promise basic cognition, recommended to see Ruan Yifeng ES6 introductory tutorial.

This article focuses on some of the key concepts that handwritten promises need to focus on.

2.1 Promise has three states:

  • pending
  • resolved
  • rejected

You can only go from Pending to Resolved or Rejected, and then the state freezes.

When the state is resolved, you need to select a value for the current Promise:

  • new Promise“, is passedresolve(val)
  • promise.then“, is passedreturn(Note that there is no explicitreturnWhen is the defaultreturn undefined)

This value can be any valid JavaScript value (including undefined, thenable object, or promise)

The Thenable object is an object or function that defines the then method

When the state is rejected, a reason should be used as the reason for the current Promise to be rejected, as in resolved.

2.2 Promise. Prototype. Then

promise.then(onFulfilled, onRejected)
Copy the code
  • Promise/A+ is the standard specification for Promises, which states that A Promise instance needs to implement only one method, THEN
  • Then takes two arguments, both of which are optional, meaning nothing can be passed, right
  • Then can be called multiple times. Calls are made sequentially, and the promise state and value are the same each time
  • Each call to THEN returns a brand new Promise instance, which can then be called chained
  • Then forms a microtask under the current macro task.

2.2.1 Status of promise

Then is similar to the Promise constructor, returning a new Promise instance.

The previous difference was that the state of the promise generated by the constructor was determined by the constructor itself:

new Promise((resolve, reject) = > {
	resolve(1) // Transfer the current state to Resolved
})
Copy the code

The state judgment of the promise returned by then needs to be divided into two steps:

  1. If the then callback can handle the state of the previous promise, otherwise the state of the previous promise can be reused directly
  2. If condition 1 is met, the current callback function can be processed properly

This is a bit convoluted, but look at the following example code to understand:

Understand condition 1:

let p1 = new Promise((resolve, reject) = > { // Promise {<rejected>: "error1"}
	reject('error1')})let p2 = p1.then(console.log) // Promise {<rejected>: "error1"}
Copy the code

P1’s state is Rejected, and P2 does not pass in the onRejected callback function, so p2’s state completely overuses P1’s state.

Understand condition 2:

let p1 = new Promise((resolve, reject) = > { // Promise {<fulfilled>: 1}
	resolve(1)})let p2 = p1.then(val= > { // Promise {<rejected>: ReferenceError: x is not defined}
	console.log('p1 was resolved:', val)
	return x; // Uncaught referenceError
})

let p3 = p2.then(undefined.reason= > 1) // Promise {<fulfilled>: 1}
Copy the code

The state of P1 is very depressing, while P2 has the callback function of ondepressing, but it is not handled correctly and the exception is thrown. Therefore, the status of P2 changes to Rejected, where reason is the reason for the error.

This will be a pity for P3, which happens to have the onRejected function and can also process it correctly. The final return value is its own value. Therefore, p3 will be fulfilled.

2.2.2 Return value of promise

As mentioned earlier, the return value of a promise can be any legal JavaScript value, including a promise, which I’ll focus on here.

Since the return value of a promise determines the value of the current promise, if the value is another promise, it means that the value is unknown and depends on the state of other promises.

Here’s another example:

let p1 = new Promise(resolve= > {
	setTimeout(resolve, 1000.1)})let p2 = new Promise(resolve= > resolve(p1))
Copy the code

P1 is a simple timer promise. After 1000ms, the state will become < depressing: 1>.

The return value of P2 is P1, so P2 is also within 1000ms, and will also become < depressing: 1> after 1000ms.

2.3 Promise. Prototype. The catch

Catch is not A standard Promise/A+ method, but it needs to be mentioned because it is one of the more commonly used methods.

Catch can be understood as an encapsulation of then:

promise.catch(function onRejected() {}) == promise.then(undefined.function onRejected() {})
Copy the code

2.4 Microtasks Microtasks

The then method is not executed immediately after the state of the current PROMISE changes. At this point, the concept of microtask was introduced.

The opposite is a macrotask, in which basic JavaScript code is executed.

You can also generate macro tasks in other ways: setTimeout, setInterval; Microtasks can be generated by promise.then, Object.observe(deprecated), and MutationObserver.

The relation between macro task and micro task is as follows (the drawing drawn by Teacher Winter in “Front End Again” is introduced here) :

That is, a macro task can have multiple microtasks.

Since the microtask mechanism is provided by the engine, setTimeout can be used instead when writing promises.

2.4.1 Parsing Tasks

When analyzing code, you can take several steps like this:

  1. Ideally, if there isn’t anysetTimeoutandpromise.thenIf so, all in one macro task
  2. If appearpromise.then, generates a microtask in the current macro task for executionpromise.then
  3. If thesetTimeout, then add a macro task that repeats condition 1

Analyze a few examples to test:

Example 1:

setTimeout(console.log, 0.0)

new Promise((resolve) = > {
    console.log(1)
    resolve(2)
}).then(console.log)

console.log(3)
Copy the code
Correct output order:

One, three, two, zero

Example 2:

console.log(8)

setTimeout(function() {
    console.log(0)
    Promise.resolve(4).then(console.log)
}) // Omit the parameter, delay defaults to 0

new Promise((resolve) = > {
    console.log(1)
    resolve(2)
}).then(console.log)

console.log(3)

setTimeout(console.log, 0.5)
Copy the code
Correct output order:

8, 1, 3, 2, 0, 4, 5

In fact, there are async/await related topics, I will improve if I read enough.

3. Write a Promise

In fact, seeing this shows that you have almost all the key concepts. All that remains is to translate the logic into code.

I wrote a copy in Github, the code logic is quite clear, you can go to see.

I recommend that you take A closer look at the Promise/A+ standard specification before writing, in conjunction with my code.

Once you understand the details clearly, write again.

If you like it, give me a “like” + star.

Thanks for reading!