twitter

Hello everyone, I’m Pan Xiao ‘an! A forever on the road to weight loss front er🐷!

See the title someone will ask ❓, you handwritten Promise handwritten, the whole New Year edition is what?

Emmm… Probably this article will use a small part of the space to write about their New Year’s resolutions πŸ“… (large probability dozen face, New Year to their own dozen chicken blood.

This year is 2022, after all, the New Year, 🎏falg🎏 should stand or stand, do not hit the face later to say, the big deal later re-set a good.

This year’s 🎏 Falg 🎏 has the following:

  • Write at least one essay a month, no limit on the content and subject, no less than 800 words, the topic is self-drafted.
  • Get up early more than 300 days a year, 365 days a year, and clock in the low-key youth group (secret organization).

  • Weekdays Forest🌲 Focus time over four hours per day.

  • By the end of the year to lose about 15 jin, the following plan notes:

    • No intake of carbohydrates in the evening, only eat vegetables πŸ₯¬ no dry rice 🍚
    • Daily life only drink water 🚰 and tea 🍡, do not drink drinks πŸ₯€ (Chinese New Year eating table can unlock the seal)
    • Fate 🏊 motion, at least two hours a week (badminton 🏸 ️ | | run πŸƒ | | keep aerobic clock)

Above 🎏falg🎏 are using video or transcript, 2023 do a clock-in video summary!

🎏falg🎏, let’s get down to business. There are a few things you might need to understand before you start writing promises, so that you can make them easier to write. You can also click here to get started.

Pre-skill points πŸ”§

This points to the problem ⬆️

When a function is called, the this argument is also passed to the function by default, in addition to the argument passed explicitly. This represents the object associated with the function call. Therefore, it is often referred to as a functional context. The orientation of this is not only related to the definition and location of the function, but also related to the way in which the function is called. Generally, function calls can be divided into the following four ways:

Called directly as a function

var name = 'Joe'
function whoAmI() {
   console.log('call my name' + ' ' + this.name)
}
whoAmI()// Call my name zhang SAN
Copy the code
"use strict"
var name = 'Joe'
function whoAmI() {
   console.log('call my name' + ' ' + this.name)
}
whoAmI()//TypeError: Cannot read properties of undefined (reading 'name')
Copy the code

This is called a direct function call to distinguish it from other calls. If a function is not called as a method, constructor, or through Apply or call, it is called as a direct function call. In non-strict mode, this refers to the global object Window (browser execution environment), but in strict mode, this refers to undefined.

As methods, associations are invoked on objects

var name = 'Joe'
const persons = {
   name: 'Outlaw maniac'.whoAmI: function () {
       console.log('call my name' + ' ' + this.name)
   }
}
persons.whoAmI()// Call my name an outlaw
Copy the code

When a function is called as a method of an object, that object becomes the context of the function and is accessible within the function with arguments

Called when a new object is instantiated

function Person(name) {
   this.name = name
   console.log('call my name' + ' ' + this.name)
}
let personins = new Person('Wang Er Ma Zi')// Call my name wang Ermazi
Copy the code

When called with the new keyword, an empty object instance is created and passed to the constructor as this. Inside the new operation, the following steps are performed:

1. Create a new empty object.

2. Point the _proto_ of the new object to the constructor’s property.

3. The object is passed to the constructor as this, which becomes the function context of the constructor and executes the statements in the constructor.

4. The newly constructed object as the return value of the new operator is discussed in the following two cases

  • If the constructor returns an object, that object is returned as the value of the entire expression, and the constructor’sthisWill be discarded.
  • However, if the constructor returns a non-object type, the return value is ignored and the newly created object is returned.

The new operator can be used to implement the new operator. The new operator can be used to implement the new operator

Through the apply,bind call

var name = 'Joe'
const persons = {
   name: 'Outlaw maniac'.whoAmI: function () {
       console.log('call my name' + ' ' + this.name)
   }
}
function whoAmI() {
   console.log('call my name' + ' ' + this.name)
}
whoAmI.call(persons)// Call my name an outlaw
Copy the code

The call and apply methods can display the specified this. The call method uses a list of arguments and the apply method uses an array:

whoAmI.call(persons,param1,param2)
whoAmI.apply(persons,[param1,param2])
Copy the code

As far as the mechanism of this is concerned, if you are interested, I recommend the following blog for further understanding:

πŸ‘†πŸ‘†πŸ‘† Yuba -JavaScript in-depth interpretation of this πŸ‘† college from the ECMAScript specification

Skill point 2: What is class ❓

We’ll use es6’s class to write the Promise constructor, using our familiar Person “class” to see what class looks like:

class Person {
 constructor(name) {
     this.name = name
 }
 getName() {
     return this.name
 }
 static eat() {
     console.log('lufei:I like to eat meat')}}Copy the code

Let’s enter this code into Babel’s online conversion editor to see what class looks like:

"use strict";
function _classCallCheck(instance, Constructor) {
 if(! (instanceinstanceof Constructor)) {
     throw new TypeError("Cannot call a class as a function"); }}function _defineProperties(target, props) {
 for (var i = 0; i < props.length; i++) {
     var descriptor = props[i];
     descriptor.enumerable = descriptor.enumerable || false;
     descriptor.configurable = true;
     if ("value" in descriptor)
         descriptor.writable = true;
     Object.defineProperty(target, descriptor.key, descriptor); }}function _createClass(Constructor, protoProps, staticProps) {
 if (protoProps) _defineProperties(Constructor.prototype, protoProps);
 if (staticProps) _defineProperties(Constructor, staticProps);
 Object.defineProperty(Constructor, "prototype", { writable: false });
 return Constructor;
}

var Person = function () {
 function Person(name) {
     _classCallCheck(this, Person);

     this.name = name;
 }

 _createClass(Person, [{
     key: "getName".value: function getName() {
         return this.name; }}], [{key: "eat".value: function eat() {
         console.log('lufei:I like to eat meat'); }}]);returnPerson; } ();Copy the code

The first function, _classCallCheck, throws an error to ensure that Person is called as a constructor

The second function, _defineProperties, is a utility function that iterates over the props variable and assigns the value to target

The third function, _createClass, takes three arguments:

  • constructorRepresents the constructor passed in, in this casePersonmethods
  • protoPropsRepresents the set of attributes that need to be assigned to the constructor prototype, in this examplegetNamemethods
  • staticPropsRepresents a collection of attributes that need to be assigned to the constructor itself (functions are also objects). In this case it iseatmethods

Conclusion:

Class is the syntactic sugar for constructors. Statements in constructor are equivalent to statements in constructors.

2. Directly defined variables are equivalent to those defined on the constructor prototype

3. Properties defined using static are defined in the constructor itself.

Skill Point 3:EventLoop EventLoop πŸŽ‰

Firstly, three sections of MDN related to the event cycle are summarized:

When executing JavaScript code, the JavaScript runtime actually maintains a set of agents for executing JavaScript code. Each agent consists of a set of execution contexts, an execution context stack, the main thread, a set of additional threads that might be created to execute the worker, a task queue, and a microtask queue.

Each agent is driven by an event loop that collects useful events (user events and other non-user events, etc.) and queues tasks so that callbacks can be performed when appropriate. It then performs all the JavaScript tasks in wait (macro tasks), then microtasks, and then performs any necessary rendering and drawing operations before starting the next loop.

  • When executing tasks from the task queue, each task in the queue is executed by the runtime at the beginning of each iteration of the new event loop. Tasks added to the queue at the beginning of each iteration will not be executed until the next iteration begins.
  • Every time a task exits and the execution context is empty, each microtask in the microtask queue is executed in turn. The difference is that it waits until the microtask queue is empty before stopping execution — even if a microtask joins in mid-stream. In other words, a microtask can add new microtasks to the queue and complete all of the microtasks before the next task starts and the current event loop ends.

These three paragraphs are actually a little difficult to understand. We can summarize them in the following three points:

  • The js code executes while maintaining a bunch of agents, which are made up of a bunch of things, including the macro task queue (task queue) and the microtask queue that we’re going to put together.

  • A time loop is responsible for collecting events, queuing them, and executing them at the appropriate time

  • The third paragraph explains the execution rules for macro and micro tasks

    • When a macro task is encountered during the execution of the macro task, the macro task is put into the macro task queue until the next iteration
    • After a macro task is executed, each microtask is executed in turn. Unlike macro task execution, new microtasks are added directly to the end of the microtask queue and then executed until the microtask queue is emptied in the current iteration.

Common macro tasks and micro tasks (if you don’t know how to use them, you can click to view the corresponding information)

Macro task Micro tasks
setTimeout MutationObserver (browser environment)
setInterval Process.nexttick (Node environment)
MessageChannel queueMicrotask
I/O, event queue requestAnimationFrame
setImmediate Promise.[ then/catch/finally ]
The script tag

We use setTimeout and queueMicrotask to help understand the event loop.

console.log('start')
setTimeout(() = > {
    console.log('settimeout')
    queueMicrotask(() = >{
        console.log('enter queueMicrotask in settimeout')})},Awesome!);
queueMicrotask(() = >{
    console.log('enter queueMicrotask1')
})
queueMicrotask(() = >{
    console.log('enter queueMicrotask2')
    queueMicrotask(() = >{
    console.log('enter queueMicrotask3')})})console.log('end')
Copy the code

With this example, we can test the theory of event cycles:

1. When a macro task (setTimeout) is encountered, place the macro task in the macro task queue. When a microtask (queueMicrotask1, queueMicrotask2) is encountered, place the microtask in the microtask queue.

2. Clear the microtask queue before executing the macro task. During the first iteration, queueMicrotask1 and queueMicrotask2 were found.

QueueMicrotask1 and queueMicrotask2 are queueMicrotask2. QueueMicrotask2 is queueMicrotask3. Microtasks encounter microtasks that are added directly to the end of the microtask queue and continue in the current iteration, so queueMicrotask3 executes next.

4. After the microtask queue is empty, we start to execute the macro task queue and execute setTimeout to add a microtask to the microtask queue. After the macro task is executed, we clear the microtask queue and print Enter queueMicrotask in setTimeout.

Skill point 4: Basic use of promises πŸ“„

If you’re not familiar with the basics, here are a few great learning sites:

  • MDN YYDS
  • Nguyen other YYDS

That’s it. Get up, drink some water, and let’s start writing promises!

Start whole live πŸ”₯

Before we start writing promises, we need to think about the basics of how promises are used in everyday development.

Leaving the static functions on promises aside, let’s think back to the basic everyday use of promises and write code that might show up in our daily development. To write us a Promise and explore the path.

let promise1=new Promise((resolve,reject) = >{
    console.log('do something')
    resolve('i')
    reject()
})
promise1.then((res) = >{
    console.log(res+'resolve after do something')},() = >{
    console.log('reject after do something')})console.log(promise1)
Copy the code

As you can see, the program prints out firstdo something“And then printedPromiseInstance, finally printedi resolve after do something. Looking at this output, we may have the following two questions:

Why is Reject not executed?

Why resolve after dosomething is executed after promise1

With these two questions, combined with the daily development and use of information, we can draw the following conclusions:

1.Promise is a constructor that can be called using the new operator.

2. Creating a Promise instance requires passing in a function that takes resolve and reject and executes immediately when the instance is created.

A Promise instance can be created from a pending state (fullfilled) and rejected (fullfilled). A Promise instance can be created from a pending state (fullfilled). A Promise instance can be created from a pending state (fullfilled). You can call reject to change the pending state from Rejected. Once the state has been changed, it cannot be changed again.

3. A Promise instance can call the then method, which takes two arguments. The first callback, onFullfilled, will be called when the Promise instance state becomes fullfileld. The other callback, onRejected, is called after the Promise instance state has changed to Reject with the reject value.

4. Functions in the then method need to be executed asynchronously

With these ideas in mind, let’s start by trying out the code in the editor!

Promise status changes πŸ€”

We can start by trying to write the Promise constructor:

class MyPromise {
   constructor(func) {
       func(resolve, reject)
   }
}
Copy the code

Next we define the three states of the Promise instance using constants, define the variables that hold the states, and initialize them as pending.

+ const PENDING = 'pending';
+ const FULFILLED = 'fulfill';
+ const REJECTED = 'rejected';
class MyPromise {
 constructor(func){+this.promisestatus = PENDING ... }... }Copy the code

Then there are the resolve and reject functions:

  • resolveThe function of the method is to bringPromiseThe state of the instance frompendigbecomefullfilledAnd theresolveThe value passed in is inPromiseFor instancepromiseresultSave it.
  • rejectThe function of thepromiseThe state of the frompendingbecomerejectedAnd therejectThe value passed in is also usedpromiseresultSave it.
  • If the state has changed, the two functions do not change the state.
class MyPromise {
    constructor(func){... +this.promiseresult = undefined
    }
+    resolve(res) {
        if (this.promisestatus == PENDING) {
            this.promisestatus=FULLFILLED
            this.promiseresult=res
            console.log('pending=>fullfilled')}} +reject(reason) {
        if (this.promisestatus == PENDING) {
            this.promisestatus=REJECTED
            this.promiseresult=reason
            console.log('pending=>rejected')}}}Copy the code

There is a small detail here, which is the introduction of this in our pre-skill points. We pass this.resolve and this.reject to func and execute. When resolve is executed, this will be undefined at that time. So we need to bind the resolve and reject methods that are passed in.

+ executor(this.resolve.bind(this), this.reject.bind(this))
Copy the code

What do I write next? In addition to reject, the Promise instance changes to Rejected. It also changes to Rejected if an exception is thrown. As a result, we can add try catch statements to our code to catch exceptions.

+       try {
+           executor(this.resolve.bind(this), this.reject.bind(this)) + +}catch (e) {
+          this.reject(e.message)
+       }
Copy the code

So far, we’ve written Promise code that looks like this:

class MyPromise {
    static PENDING = 'pending';
    static FULFILLED = 'fulfilled';
    static REJECTED = 'rejected';
    constructor(func) {
        this.promisestatus = PENDING
        this.promiseresult = null
        try {
            func(this.resolve.bind(this), this.reject.bind(this))}catch (erro) {
            this.reject(erro)
        }
    }
    resolve() {
        if (this.promisestatus == PENDING) {
            this.promisestatus = FULLFILLED
            this.promiseresult = res
            console.log('pending=>fullfilled')}}reject() {
        if (this.promisestatus == PENDING) {
            this.promisestatus = REJECTED
            this.promiseresult = reason
            console.log('pending=>rejected')}}}Copy the code

It’s starting to look like Promise. So far, everything has gone according to plan. Do some eye exercises and continue the handwritten journey promised

The then method implements πŸ₯΄

With the Promise state switched, it’s time to deal with the then methods. The then method takes two arguments: onFullfilled (the callback function called when the status changes to Fullfilled) and onRejected (the callback function called when the status changes to Rejected). Before implementing THEN, let’s look at the relevant parts of Promises/A+ specification for THEN.

specification

Promises/A+

The Promise/A+ specification

The specification defines the logic and details of the THEN method in detail. We will implement the THEN method step by step from 2.2.

  • 2.2.1 onFulfilled ε’Œ onRejectedBoth are optional parameters

    • 2.2.1.1. If ondepressing is not a function, it must be ignored

    • 2.2.1.2. If onRejected is not a function, it must be ignored

  • 2.2.2. IfonFulfilledIt’s a function

    • 2.2.2.1. It must be inpromiseCalled after being resolved,promiseAs its first argument.
    • 2.2.2.2. It must not be inpromiseCalled before being resolved.
    • 2.2.2.3. It must not be called more than once.
  • 2.2.3. If onRejected is a function

    • 2.2.3.1. It must be inpromiseCalled after being rejected and usedpromiseAs its first argument.
    • 2.2.3.2. It must not be called before a promise has been rejected.
    • 2.2.3.3. It must not be called more than once.
  • 2.2.4. OnFulfilled or onRejected must not be called until only the platform code is included in the implementation context stack.

  • OnFulfilled and onRejected must be called as a function (without this value).

  • 2.2.6 Then on the same Promise may be invoked multiple times.

    • If 2.2.6.1.promiseBe solved, all correspondingonFulfilledCallbacks must be called as they were originally calledthenSequential execution of
    • 2.2.6.2. IfpromiseRejected, all correspondingonRejectedCallbacks must be called as they were originally calledthenSequential execution of
  • 2.2.7 thenMust return onepromise.
promise2 = promise1.then(onFulfilled,onRejected)
Copy the code
  • 2.2.7.1. If ondepressing or onRjected returns a value x, run the promise settlement [[Resolve]](promise2,x)

  • 2.2.7.2. If onFulfilled or onRejected throws an exception E, promise2 must use E as the reason to be rejected

  • 2.2.7.3. If ondepressing is not a function and promise1 is resolved, promise2 must be resolved with the same value as promise1

  • 2.2.7.4. If onRejected is not a function and promise1 is rejected, promise2 must be rejected with the same reason as promise1

Normative analysis

Specifications 2.2.1 and 2.2.5

This is a big pity. When onFulfilled or onRejected is not a function, it must be ignored, and onFulfilled and onRejected must be called as a function. So when we meet the onFulfilled which is not a function, Promiseresult () returns a promiseresult () exception (Reason) when onRejected is not a function.

+ then(onFulfilled, onRejected) {
+    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : promiseresult= > promiseresult;
+    onRejected = typeof onRejected === 'function' ? onRejected : reason= >{+throwreason; +}; +}Copy the code

Specification 2.2.2 & Specification 2.2.3 & Specification 2.2.6

In combination with specification 2.2.2 and 2.2.3, we can draw the following two conclusions:

  1. onFulfilled ε’Œ onRejectedMust be called after a state change,onFulfilledParameters forpromiseThe value of theonRejectedParameters forpromiseThe value of the.
  2. onFulfilled ε’Œ onRejectedCannot be called more than once.
 then(){...if (this.promisestatus == FULLFILLED) {
         onFulfilled(this.promiseresult)
     }
   if (this.promisestatus == MyPromise.RJECTED) {
         onRejected(this.promiseresult)
     }
 }
Copy the code

Is the state of a promise still pending when you call then? Not only are there, but they are common. For example, everyday use of promise to encapsulate Ajax requests has the following code:

function getData() {
    return new Promise((resolve, reject) = > {
        someajax((res,message) = > {
            if (res.code == '200') {
                resolve(res)
            } else {
                reject('message')
            }
        })
    })
}
getData().then((res) = > {
    console.log(res)
})
Copy the code

The then method is executed, but Ajax is still retrieving data asynchronously. Resolve is executed after the THEN method. How do we make promises that live up to our expectations?

The answer is: use variablesonFulfilled ε’Œ onRejectedSave it and waitpromiseDeal with it when the state changes.

So the new question comes. What data type should we use to save onFulfilled and onRejected? We can move down to specification 2.2.6.

Analysis of specification 2.2.6:

  • The samePromiseThe instancethenMay be called multiple times, and all correspondingonFulfilled ε’Œ onRejectedMust be called as they were originally calledthenSequential execution of

To sum up, we need a queue to hold all the onFulfilled and onRejected. After the promise state really changes, we will call it in the order of first in, first out, so we can write the following code:

    constructor(executor){...this.onFulfilleds = []
        this.onRejecteds = []
        ...
    }
    resolve(res) {
        if (this.promisestatus == PENDING) {
            this.promisestatus = FULFILLED
            this.promiseresult = res
     +       while (this.onFulfilleds.length > 0) {+this.onFulfilleds.shift()(this.promiseresult)
     +       }
        }
    }

    reject(reason) {
        if (this.promisestatus == PENDING) {
            this.promisestatus = REJECTED
            this.promiseresult = reason
     +      while (this.onRejecteds.length > 0) {+this.onRejecteds.shift()(this.promiseresult)
     +     }
        }
    }
    then(onFulfilled, onRejected) {
        if (this.promisestatus == PENDING) {
      +     this.onFulfilleds.push(onFulfilled)
      +     this.onRejecteds.push(onRejected)
        }
        ...
    }
Copy the code

Specification 2.2.4

OnFulfilled and onRejected can only be executed when the implementation context stack contains only platform code.

The specification explains the platform code in one sentence:

Here “platform code” means engine, environment, and promise implementation code. In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, This can be implemented with either a “macro-task” mechanism such as setTimeout or setImmediate, Or with a “micro-task” mechanism such as MutationObserver or process.nexttick. Since the promise implementation is considered platform code, It may itself contain a task-scheduling queue or “trampoline” in which the handlers are called.

Here “platform code” means engine, environment, andpromiseThe implementation code. In practice, this needs to be ensuredonFulfilledandonRejectedExecute asynchronously and should be executed inthenThe event loop in which the method is called is then executed with the new execution stack. This can be used assetTimeoutorsetImmediateSuch a “macro task” mechanism is implemented, or as followsMutationObserverorprocess.nextTickSuch a “microtask” mechanism is realized. Due to thepromiseThe implementation of “platform code” is considered “platform code” and thus may already contain a task scheduling queue when its own handler is called

Combined with the event cycle mentioned in the pre-skill point, we can understand it as follows:

onFulfilled ε’Œ onRejectedNeed to be inthenIs executed at the end of the event cycle in which the method is called. And to do that,onFulfilled ε’Œ onRejectedThis can be done using either macro tasks or microtasks.

We can use the native Promise demo to further understand this:

// The ondepressing method is executed in the new loop following the then loop
let promiseins = new Promise((resolve, reject) = > {
    resolve('fullfilled');
})
console.log('event loop turn in whichΒ `then`Β is called start')
promiseins.then(() = > {
    console.log('onFulfilled called')})setTimeout(() = > {
    console.log('timeout called')})console.log('event loop turn in whichΒ `then`Β is called end ')
// event loop turn in whichΒ `then`Β is called start
// event loop turn in whichΒ `then`Β is called end   
// onFulfilled called
// timeout called
Copy the code

Here, in order to achieve a better approach to the original Promise implementation, we use micro-tasks to realize the asynchronous call of onFulfilled and onRejected. Remember that onFulfilled and onRejected also need asynchronous processing when the state is PENDING and the function is put into the queue.

then(){... +if (this.promisestatus == MyPromise.PENDING) {
+    this.onFulfilleds.push(() = > {
+        q(() = > {
+            onFulfilled(this.promiseresult) + }); + +})this.onRejecteds.push(() = > {
+        queueMicrotask(() = > {
+            onRejected(this.promiseresult) + }); + +}})if (this.promisestatus == FULLFILLED) {
+    queueMicrotask(() = > {
        onFulfilled(this.promiseresult)
+    })
}
if (this.promisestatus == MyPromise.RJECTED) {
+    queueMicrotask(() = > {
        onRejected(this.promiseresult)
+    })
}}
Copy the code

Specification 2.2.7

Before we begin, let’s take a look at the current completion of THEN, because once construction specification 2.2.7 starts, the previously completed THEN method may be changed beyond recognition.

then(onFulfilled, onRejected){...if (this.promisestatus == MyPromise.PENDING) {
        this.onFulfilleds.push(() = > {
            queueMicrotask(() = > {
                onFulfilled(this.promiseresult)
            });
        })
        this.onRejecteds.push(() = > {
            queueMicrotask(() = > {
                onRejected(this.promiseresult) }); })}if (this.promisestatus == MyPromise.FULLFILLED) {
        queueMicrotask(() = > {
            onFulfilled(this.promiseresult)
        })
    }
    if (this.promisestatus == MyPromise.RJECTED) {
        queueMicrotask(() = > {
            onRejected(this.promiseresult)
        })
    }
}
Copy the code

This last specification is the key to the chain of calls that implement promises, and is personally the steepest hill on the whole handwritten Promise journey. The difficulty is that the new Promise instance returned (denoted by then_PROMISE in this article) needs to determine its status and return value based on the execution result X of onFullfilled and onRejected. Specifications 2.2.7.1 to **2.2.7.4 ** describe how different cases are handled, referring to a promise resolver [[Resolve]](then_PROMISE,x) that is subsequently called resolvePromise, We’ll talk about 🀌 separately later.

Reading specification 2.2.7 item by item, we have the following analysis:

  • First we have to create a new onePromiseInstance used to return
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason= > {
            throw reason;
        };
+        const then_promise = new MyPromise((resolve, reject) = > {
+       })
+        return then_promise
    }
Copy the code
  • Then_promise = then_PROMISE = onFullfilled = onRejected = then_PROMISE = then_PROMISE = then_PROMISE = then_PROMISE = then_PROMISE = then_PROMISE = then_PROMISE = then_PROMISE = then_PROMISE = then_PROMISE = then_PROMISE = then_PROMISE = then_PROMISE = then_PROMISE = then_PROMISE = then_PROMISE = then_PROMISE = then_PROMISE = then_PROMISE = then_PROMISE = then_PROMISE

  • In specification 2.2.7.2, ondepressing and onRejected are required to be captured with exceptions, and exceptions are thrown when there are errors, which are used as the reject parameter of THEN_PROMISE.

  • The onFullfilled and onRejected functions described in 2.2.7.3 and 2.2.7.4 are not included in the then method, so it can be included in 2.2.7.1. ResolvePromise.

Based on the above three analyses, you can write the following code:

then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : promiseresult= > promiseresult;
    onRejected = typeof onRejected === 'function' ? onRejected : reason= > {
        throw reason;
    };
    const then_promise = new MyPromise((resolve, reject) = > {
        if (this.promisestatus == MyPromise.PENDING) {
            this.onFulfilleds.push(() = > {
                queueMicrotask(() = > {
                    try {
                        let x = onFulfilled(this.promiseresult)
                        this.promiseresolve(then_promise, x, resolve, reject)}catch (e) {
                        reject(e)
                    }
                });
            })
            this.onRejecteds.push(() = > {
                queueMicrotask(() = > {
                    try {
                        let x = onrejected(this.promiseresult)
                       this.promiseresolve(then_promise, x, resolve, reject)}catch(e) { reject(e) } }); })}if (this.promisestatus == MyPromise.FULLFILLED) {
            queueMicrotask(() = > {
                try {
                    let x = onFulfilled(this.promiseresult)
                    this.promiseresolve(then_promise, x, resolve, reject)}catch (e) {
                    reject(e)
                }
            });

        }
        if (this.promisestatus == MyPromise.RJECTED) {
            queueMicrotask(() = > {
                try {
                    let x = onrejected(this.promiseresult)
                    this.promiseresolve(then_promise, x, resolve, reject)}catch(e) { reject(e) } }); }})return then_promise
}
}
 promiseresolve(then_promise, x, resolve, reject){}Copy the code

The whole then method can be broken down into several small logic to understand:

  • onFulfilled ε’Œ onRejectedIf it’s not a function, it’s a function.
  • PENDINGState,onFulfilledPut it in the queue, but use it before you put it intry catcH catches exceptions,queueMicrotaskImplement asynchrony and do the restpromiseresolveTo deal with,onRejectedThe processing logic is similar.

So we can rearrange the code to look like this:

    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason= > {
            throw reason;
        };
        const then_promise = new MyPromise((resolve, reject) = >{+const fulfillcallback = () = > {
                queueMicrotask(() = > {
                    try {
                        const x = onFulfilled(this.promiseresult)
                        this.resolvePromise(then_promise, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                });
            }
+            const rejectedcallback = () = > {
                queueMicrotask(() = > {
                    try {
                        const x = onRejected(this.promiseresult)
                        this.resolvePromise(then_promise, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                });
            }
            if (this.promisestatus == FULFILLED) {
                fulfillcallback()
            }
            else if (this.promisestatus == REJECTED) {
                rejectedcallback()
            }
            else {
                this.onFulfilleds.push(fulfillcallback)
                this.onRejecteds.push(rejectedcallback)
            }
        })


        return then_promise
    }
Copy the code

The last level, how to write the promiseresolve function? Don’t worry, there are rules!

Implementation of Promiseresolve 🀒

specification

  • 2.3.1ifpromiseandxReference the same object, use oneTypeErrorRefuse as a reasonpromise
  • 2.3.2. IfxIs apromise, adopt its state: [3.4]
    • 2.3.2.1. IfxisWaiting for theState,promiseYou have to wait untilxbeTo solveorRefused to
    • 2.3.2.2. IfxisTo solveState, with the same valueTo solvepromise
    • 2.3.2.3. IfxisRefused toState, for the same reasonRefused topromise
  • 2.3.3Otherwise, ifxIs an object or function
    • 2.3.3.1 Make THEN x.teng.

    • 2.3.3.2. If retrieving the attribute x.teng causes an exception E to be thrown, reject the promise with e as the reason

    • 2.3.3.3. If then is a function, call it with x as this. The then method takes two callbacks, the first called resolvePromise and the second called rejectPromise:

      • If 2.3.3.3.1.resolvePromiseWith a valueyCall, run[[Resolve]](promise, y). Translator’s note: Here again called[[Resolve]](promise,y)Because theyIs likely to bepromise
      • If 2.3.3.3.2.rejectPromiseUse one reasonrCall,rRefused topromise. IfrforpromiseIf it were, it would still be directreject.Refused toThe reason is thatpromise. Don’t wait untilpromisebeTo solveorRefused to
      • If 2.3.3.3.3.resolvePromiseandrejectPromiseIf the same parameter is called multiple times, the first call takes precedence and subsequent calls are ignored. Translator’s note: This is mainly aimed atthenable.promiseOnce changed, the state of the.
      • 2.3.3.3.4 If calledthenAn exception was throwne.
        • If 2.3.3.4.1.resolvePromiseorrejectPromiseAlready called, ignore it
        • 2.3.3.4.2. OtherwiseeReject as a causepromise
    • 2.3.3.4. If then is not a function, solve the promise with x

  • 2.3.4. IfxInstead of an object or function, usexTo solvepromise

Normative analysis

First of all, we know that the promiseresolve function is used to better get the result of then_promise in different x cases. The entire specification for the promiseresolve function is quite long, but it is summarized to talk about multiple cases of X:

X is the same object as then_PROMISE

That x returns then_PROMISE itself, creating a circular reference (as shown below), so it can’t be equal to itself.

As a result, we can be inresolvepromiseWrite down the specification in2.3.1The implementation of the

    resolvePromise(then_promise, x, resolve, reject) {
        if (then_promise === x) {
            return reject(new TypeError('Don't loop references')); }}Copy the code

X is a promise

  • ifxIs the waiting state,then_promiseWill be waiting forxTo be resolved or rejected;
  • xIf it is the solution state, thenthen_promiseLet’s do it with the same value
  • xIs the reject state, thenthen_promiseReject with the same value

What’s harder to understand is how then_PROMISE waits for X to be resolved or rejected when x is in the wait state. How do I get x in the promiseresult that uses the same value? The answer is to call the THEN method of X and pass then_PROMISE and then_PROMISE itself resolve and reject as arguments. Fortunately, in subsequent code, Make sure that the promiseresult of X (represented by y in the specification) can remain in contact with then_PROMISE and change the state of THEN_promise at any time.

The other two cases seem easy to understand, using 'then_promise' 'resolve' and 'reject' for 'X' 'promiseresult', so we can try writing code like this:Copy the code
    resolvePromise(then_promise, x, resolve, reject) {
        if (then_promise === x) {
            return reject(new TypeError('Don't loop references'));
        }
+        //x is a promise
+        else if (x instanceof MyPromise) {
+            if (x.promisestatus == FULFILLED) {
+                resolve(x.promiseresult)
+                return+},if (x.promisestatus == REJECTED) {
+                reject(x.promiseresult)
+                return
+            }
+            x.then((y) = >{+this.resolvePromise(then_promise, y, resolve, reject)
+            }, r= > reject(r))
+        }
}
Copy the code

This looks like a good spec, but there’s a problem. The promiseresult type of X is unknown, it may still be a promise, for example, we can use the native demo and written code to test the demo and see the printed result:

let p1 = new Promise((resolve, reject) = > {
    resolve('I am a native nesting doll number one πŸͺ†! ')})let p2 = p1.then(() = > {
    return new Promise((resolve,reject) = >{
        resolve(new Promise((resolve,reject) = >{
            resolve('I am native nesting doll number two πŸͺ†! ')})}})))console.log(p2)
/* Promise {promisestatus: depressing, promiseresult: I am native nesting doll no. 2 πŸͺ†! } * /



let p1 = new MyPromise((resolve, reject) = > {
    resolve('I am pingti doll no. 1 πŸͺ†! ')})let p2 = p1.then(() = > {
    return new MyPromise((resolve,reject) = >{
        resolve(new MyPromise((resolve,reject) = >{
            resolve('I am Pingti matryoshka # 2 πŸͺ†! ')})}})))console.log(p2)
/* mypromise:{promisestatus: depressing, promiseresult:{promiseresult:' I am pingtiyuwa no. 2 πŸͺ†! ', promisestatus:fulfilled } } */
Copy the code

So the conclusion is: Regardless of the state of the promise (x), we call x’s then method to recursively invoke resolvePromise from x’s promiseresult as the new X. Until we get out of the loop condition of x instanceof MyPromise.

    resolvePromise(then_promise, x, resolve, reject){... +//x is a promise
+        else if (x instanceof MyPromise) {
             ...
+            x.then((y) = >{+this.resolvePromise(then_promise, y, resolve, reject)
+            }, r= > reject(r))
+        }
}
Copy the code

X is either an object or a function (which is also an object), and null is excluded, which falls into the fourth case

  • 2.3.3.1 ε’Œ 2.3.3.2Let’s merge and try to getx.thenAnd retrieves exceptions, if anyeReject as a causethen_promiseIf successful to getx.thenAssigned tothenThis operation is commented in the specification3.5In the explanation, probably means that firstx.thenGet the reference to prevent afterx.thenChanges occur, and the preassignment can be ensuredthenThe variables are always the same.
 else if(x ! = =null && typeof x === 'object' || typeof x === 'function') {
            try {
                const then = x.then;
            } catch (e) {
                returnreject(e); }}Copy the code
  • Then is a method, use x as this call then, has two parameters, respectively is resolvePromise and rejectPromise resolvePromise call with a new x as parameters, The rejectPromise takes reason R as an argument and internally calls reject.

  • We also need to add a parameter to determine whether resolvePromise and rejectPromise have been called. If they have been called, the first call should prevail and subsequent calls should be ignored.

  • Then if that’s not a method, use x to solve then_promise.

else if(x ! = =null && typeof x === 'object' || typeof x === 'function') {
           let hascalled=false
           try {
               const then = x.then;
              if (typeof then === 'function') {
                   then.call(
                       x,
                       y= > {
                           if(hascalled)return;
                           hascalled=true
                           this.resolvePromise(then_promise, y, resolve, reject);
                       },
                       r= > {
                           if(hascalled)return;
                           hascalled=truereject(r); })}else {
                   if(hascalled)return;
                   hascalled=trueresolve(x); }}catch (e) {
               if(hascalled)return;
               hascalled=true
               returnreject(e); }}Copy the code

X is a plain other thing

resolvePromise(then_promise, x, resolve, reject){...else {
            // use x directly to resolve the promise
            returnresolve(x); }}Copy the code

Full code βœ…

At this point, the handwritten Promise journey is basically over. Here’s a look at the complete code for the handwritten Promise:

const PENDING = 'pending';
const FULFILLED = 'fulfill';
const REJECTED = 'rejected';
class MyPromise {
    constructor(executor) {
        this.promisestatus = PENDING
        this.promiseresult = undefined
        this.onFulfilleds = []
        this.onRejecteds = []
        try {
            executor(this.resolve.bind(this), this.reject.bind(this))}catch (e) {
            this.reject(e.message)
        }
    }
    resolve(res) {
        if (this.promisestatus == PENDING) {
            this.promisestatus = FULFILLED
            this.promiseresult = res
            while (this.onFulfilleds.length > 0) {
                this.onFulfilleds.shift()(this.promiseresult)
            }
        }
    }

    reject(reason) {
        if (this.promisestatus == PENDING) {
            this.promisestatus = REJECTED
            this.promiseresult = reason
            while (this.onRejecteds.length > 0) {
                this.onRejecteds.shift()(this.promiseresult)
            }
        }
    }
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason= > {
            throw reason;
        };
        const then_promise = new MyPromise((resolve, reject) = > {
            const fulfillcallback = () = > {
                queueMicrotask(() = > {
                    try {
                        const x = onFulfilled(this.promiseresult)
                        this.resolvePromise(then_promise, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                });
            }
            const rejectedcallback = () = > {
                queueMicrotask(() = > {
                    try {
                        const x = onRejected(this.promiseresult)
                        this.resolvePromise(then_promise, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                });
            }
            if (this.promisestatus == FULFILLED) {
                fulfillcallback()
            }
            else if (this.promisestatus == REJECTED) {
                rejectedcallback()
            }
            else {
                this.onFulfilleds.push(fulfillcallback)
                this.onRejecteds.push(rejectedcallback)
            }
        })
        return then_promise
    }
    resolvePromise(then_promise, x, resolve, reject) {
        if (then_promise === x) {
            return reject(new TypeError('Don't loop references'));
        }
        else if (x instanceof MyPromise) {
            x.then((y) = > {
                this.resolvePromise(then_promise, y, resolve, reject)
            }, r= > reject(r))
        }
        else if(x ! = =null && typeof x === 'object' || typeof x === 'function') {
            let called = false;
            try {
                const then = x.then;
                if (typeof then === 'function') {
                    then.call(
                        x,
                        y= > {
                            if (called) return;
                            called = true;
                            this.resolvePromise(then_promise, y, resolve, reject);
                        },
                        r= > {
                            if (called) return;
                            called = true; reject(r); })}else {
                    if (called) return;
                    called = true; resolve(x); }}catch (e) {
                if (called) return;
                called = true;
                returnreject(e); }}else {
            returnresolve(x); }}}Copy the code

πŸ“œ PromiseA + testing

Since it is based on the A+ specification, we must see if we can pass the test. Promises + aPlus-Tests: Promises + APlus-Tests: Promises + APlus-Tests: Promises + APlus-Tests: Promises + APlus-Tests: Promises + APlus-Tests

Then write down the Adapter that meets the requirements in a handwritten JS file

MyPromise.deferred = function () {
    let result = {};
    result.promise = new MyPromise((resolve, reject) = > {
        result.resolve = resolve;
        result.reject = reject;
    });
    return result;
}

module.exports = MyPromise;
Copy the code

Aplus-tests MyPromise = NPX promises-aplus-tests MyPromise = NPX promises-aplus-tests MyPromise = NPX Promises -aplus-tests MyPromise = NPX Promises -aplus-tests MyPromise = NPX Promises -aplus-tests MyPromise

Discussion about the topic selection πŸ—£

One month ago, after seeing Rock’s handwritten Promise article, we had an in-depth communication about how to write the article, whether we should deliberately avoid similar articles, and whether timer should be added to resolve. We used wechat voice friendly communication for more than half an hour. Finally, I used the knowledge points I learned from Rock’s article. With my own understanding and data collection, it took nearly three weeks to come up with this article. It’s exhausting, but it’s rewarding.

Smells good? 🀀

In the last month or so, I accidentally chatted with Nezha’s fans about MAC development or Windows development. In line with the spirit of no use, no voice core, and the end of the year is approaching, in the hometown office (although finally can not go back) has become a just need, the pain of cruel heart into the MAC Pro.

Conclusion: The initial use of the MAC will be a little uncomfortable, after a little used to it, you will feel that the MAC is relatively smooth in operation and performance, many features are still to be developed, so far I feel good.

Advice from the big guys πŸ₯‚

Before writing the article, I also consulted several gold diggers about learning and writing. Including the input method configuration Chinese and English blank, the necessity of Posting large paragraph specification and so on questions and ideas, the big guys are very enthusiastic to help answer the question. In this month’s chatting with everyone, I have made a summary and reflection on my previous study and life, and also learned many effective learning methods and good learning habits. In order to avoid the suspicion of being hot, I will not name you one by one. In a word, thank you!

If you have any questions or suggestions about the wording, knowledge points and article format, please feel free to let me know in the comments section. I am very happy to make friends and chat with everyone in the community.

πŸŽ‰πŸŽ‰ Happy New Year to you all! In the New Year health, wages up! πŸŽ‰ πŸŽ‰

Refer to the article πŸ“„

Javascript Ninja Secrets (2nd edition)

A + specification

A+ standard Chinese translation

9 k word | Promise/async/Generator principle parsing

Promises/A+ 872 Official Test Cases

Start with a Promise interview question that keeps me up at night and dive into the Promise implementation details

Written Promise in The PromiseA+ Specification: Microtasks and Javascript runtime environments