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’s
this
Will 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:
constructor
Represents the constructor passed in, in this casePerson
methodsprotoProps
Represents the set of attributes that need to be assigned to the constructor prototype, in this examplegetName
methodsstaticProps
Represents a collection of attributes that need to be assigned to the constructor itself (functions are also objects). In this case it iseat
methods
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 printedPromise
Instance, 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:
resolve
The function of the method is to bringPromise
The state of the instance frompendig
becomefullfilled
And theresolve
The value passed in is inPromise
For instancepromiseresult
Save it.reject
The function of thepromise
The state of the frompending
becomerejected
And thereject
The value passed in is also usedpromiseresult
Save 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
εonRejected
Both 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. If
onFulfilled
It’s a function- 2.2.2.1. It must be in
promise
Called after being resolved,promise
As its first argument. - 2.2.2.2. It must not be in
promise
Called before being resolved. - 2.2.2.3. It must not be called more than once.
- 2.2.2.1. It must be in
-
2.2.3. If onRejected is a function
- 2.2.3.1. It must be in
promise
Called after being rejected and usedpromise
As 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.3.1. It must be in
-
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.
promise
Be solved, all correspondingonFulfilled
Callbacks must be called as they were originally calledthen
Sequential execution of - 2.2.6.2. If
promise
Rejected, all correspondingonRejected
Callbacks must be called as they were originally calledthen
Sequential execution of
- If 2.2.6.1.
- 2.2.7
then
Must 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:
onFulfilled
εonRejected
Must be called after a state change,onFulfilled
Parameters forpromise
The value of theonRejected
Parameters forpromise
The value of the.onFulfilled
εonRejected
Cannot 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
ε onRejected
Save it and waitpromise
Deal 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 same
Promise
The instancethen
May be called multiple times, and all correspondingonFulfilled
εonRejected
Must be called as they were originally calledthen
Sequential 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, and
promise
The implementation code. In practice, this needs to be ensuredonFulfilled
andonRejected
Execute asynchronously and should be executed inthen
The event loop in which the method is called is then executed with the new execution stack. This can be used assetTimeout
orsetImmediate
Such a “macro task” mechanism is implemented, or as followsMutationObserver
orprocess.nextTick
Such a “microtask” mechanism is realized. Due to thepromise
The 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
ε onRejected
Need to be inthen
Is executed at the end of the event cycle in which the method is called. And to do that,onFulfilled
ε onRejected
This 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 one
Promise
Instance 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
εonRejected
If it’s not a function, it’s a function.PENDING
State,onFulfilled
Put it in the queue, but use it before you put it intry catc
H catches exceptions,queueMicrotask
Implement asynchrony and do the restpromiseresolve
To deal with,onRejected
The 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.1if
promise
andx
Reference the same object, use oneTypeError
Refuse as a reasonpromise
- 2.3.2. If
x
Is apromise
, adopt its state: [3.4]- 2.3.2.1. If
x
isWaiting for theState,promise
You have to wait untilx
beTo solveorRefused to - 2.3.2.2. If
x
isTo solveState, with the same valueTo solvepromise
- 2.3.2.3. If
x
isRefused toState, for the same reasonRefused topromise
- 2.3.2.1. If
- 2.3.3Otherwise, if
x
Is 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.
resolvePromise
With a valuey
Call, run[[Resolve]](promise, y)
. Translator’s note: Here again called[[Resolve]](promise,y)
Because they
Is likely to bepromise
- If 2.3.3.3.2.
rejectPromise
Use one reasonr
Call,r
Refused topromise
. Ifr
forpromise
If it were, it would still be directreject
.Refused toThe reason is thatpromise
. Don’t wait untilpromise
beTo solveorRefused to - If 2.3.3.3.3.
resolvePromise
andrejectPromise
If 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
.promise
Once changed, the state of the. - 2.3.3.3.4 If called
then
An exception was throwne
.- If 2.3.3.4.1.
resolvePromise
orrejectPromise
Already called, ignore it - 2.3.3.4.2. Otherwise
e
Reject as a causepromise
- If 2.3.3.4.1.
- If 2.3.3.3.1.
-
2.3.3.4. If then is not a function, solve the promise with x
-
- 2.3.4. If
x
Instead of an object or function, usex
To 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 inresolvepromise
Write 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
- if
x
Is the waiting state,then_promise
Will be waiting forx
To be resolved or rejected; x
If it is the solution state, thenthen_promise
Let’s do it with the same valuex
Is the reject state, thenthen_promise
Reject 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 get
x.then
And retrieves exceptions, if anye
Reject as a causethen_promise
If successful to getx.then
Assigned tothen
This operation is commented in the specification3.5In the explanation, probably means that firstx.then
Get the reference to prevent afterx.then
Changes occur, and the preassignment can be ensuredthen
The 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