Since I am transitioning to TypeScript, I will continue to type in TypeScript for this development.
This should be the last time I share a TypeScript post with a TypeScript name. Goodbye 🤞, I’m safe on my way. (So many times, get on the bus, but not many people get on the bus, so I’ll go first.)
This article is for readers who know Promise from scratch or want to delve deeper into it and learn:
-
Make promises
-
Promise API implementation method
-
The front end is no sense of security
I hope readers can have a deep understanding of promises and implement a Promise class on their own just by reading this article. Therefore, I will tell Promise from all aspects, and the content may be more, so I suggest readers to read it.
Why implement promises
Promises appear in Es6, and promise-polyfill is usually used if Es5 requires promises. In other words, we want to implement a polyfill. Implementing it not only helps us learn more about promises but also reduces the chances of making mistakes in use, leading to Promise best practices.
On the other hand, re-implementing a dependency is also a way of interpreting the source code. To do so consistently means improving the ability to interpret the source code.
Promise
A Promise represents the end result of an asynchronous operation and interacts with it primarily through the then method, which registers two callback functions to receive the end value of a Promise or the reason why the Promise cannot be implemented.
Take a look at an API structure diagram that the author drew with his heart (if you can’t see it clearly, you can see it on GitHub, there is a large picture and xMind source file) :
The diagram above is just an API blueprint for Promises. The Promises/A+ specification does not design how Promises are created, resolved, and rejected. Instead, it focuses on providing A universal then approach. As A result, the implementation of the Promise/A+ specification can coexist well with less formal but usable implementations. If everyone followed the spec, there would be fewer compatibility issues. Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+
Promises/A+
Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises Here are some important points in the specification
The term
-
Promise an object or function that has then methods and behaves in ways that Promise /A+ Promises;
-
Thenable An object or function that defines a THEN method, also known as having a THEN method.
-
Value refers to any valid JavaScript value (including undefined, thenable, and promise).
-
Exception A value thrown by a throw statement
-
A. reason B. reason C. reason D. reason
The state of the Promise
The current state of a Promise must be one of the following three states: Pending, Fulfilled and Rejected.
-
Pending state
In the wait state, the promise needs to satisfy:
can
Migrate to execute or reject state -
This is a big pity.
When in the execution state, promise should satisfy:
Can't
To migrate to any other state, you must have oneimmutable
theFinal value
-
(Rejected)
In the rejection state, the promise must satisfy:
Can't
To migrate to any other state, you must have oneimmutable
theAccording to the due
Immutable here refers to identity (which can be used to determine equality with ===), not immutable at a deeper level (where value or Reason is not a fundamental value, but only reference addresses are required to be equal, but attribute values can be changed).
Then method
A promise must provide a THEN method to access its current value, final value, and reason.
Promise’s then method accepts two arguments:
promise.then(onFulfilled, onRejected);
Copy the code
-
OnFulfilled and onRejected are both optional parameters.
-
If ondepressing is a function, it must be called after the promise execution ends, and its first parameter is the final value of the promise. It cannot be called before the promise execution ends, and the number of calls cannot exceed one time
-
If onRejected is a function, it must be called after the promise is rejected. Its first parameter is the basis of the Promise. It cannot be called before the promise is rejected and the number of calls cannot exceed one time
-
OnFulfilled and onRejected can only be called if the implementation environment stack contains only the platform code (meaning the engine, the environment, and the promise implementation code)
-
In practice, ensure that the onFulfilled and onRejected methods are executed asynchronously and should be executed in a new execution stack after the event loop in which the THEN method is called.
-
Ondepressing and onRejected must be called as a function that has no this value (i.e., in strict mode, the value of this is undefined; It is a global object in non-strict mode.
-
The then method can be called multiple times by the same promise
-
The then method must return a Promise object
The Then argument (function) returns a value
I hope readers can read this part carefully, which will be of great help to understand the THEN method of promise.
Let’s start with the promise implementation process:
The general process is that the promise changes from pending to fulfilled or Rejected, and then calls the onFulfilled or onRejected parameter of the THEN method to return the Promise object.
To further understand, assume two promises:
promise2 = promise1.then(onFulfilled, onRejected);
Copy the code
There will be the following situations:
-
If ondepressing or onRejected throws exception E, promise2 must reject and return rejection cause E
-
If ondepressing is not a function and promise1 executes successfully, promise2 must execute successfully and return the same value
-
If onRejected is not a function and promise1 rejects it, promise2 must reject it and return the same data
To get a feel for this, copy the following code to the Chrome console or other executable environment:
// Switch the state of promise1 by changing isResolve
const isResolve = true;
const promise1 = new Promise((resolve, reject) = > {
if (isResolve) {
resolve('promise1 Execution state ');
} else {
reject('Promise1 reject state'); }});// Promise1 (resolve); ondepressing throws an exception
// promise2 must reject execution and return the reject cause
promise1
.then((a)= > {
throw 'Throw an exception! ';
})
.then(
value= > {
console.log(value);
},
reason => {
console.log(reason); });// Promise1 is a function that is resolved and ondepressing is not a function
// promise2 must execute successfully and return the same value
promise1.then().then(value= > {
console.log(value);
});
// promise1 is reject and onRejected is not a function
// promise2 must reject execution and return rejection
promise1.then().then(
(a)= > {},
reason => {
console.log(reason); });// Promise1 returns a value in resolve and ondepressing
promise1
.then(value= > {
return value;
})
.then(value= > {
console.log(value);
});
Copy the code
Here is another important case that relates to the issue of value passing in a business scenario:
onFulfilled
oronRejected
returnA JavaScript valid valueIn the case
Let’s assume that there is a method inside the THEN method called [[Resolve]] to handle this special case. Let’s look at this method in detail.
[[Resolve]]
methods
Methods like [[…]] are generally considered internal implementations, such as [[Resolve]], which takes two arguments:
[[Resolve]](promise, x);
Copy the code
For x values, there are several cases:
-
X has the then method and looks like a Promise
-
X is an object or function
-
X to Promise
In addition, promise cannot be equal to x, i.e. promise! == x, otherwise:
Here’s a picture to get a general idea of how to deal with each situation:
Promise/A+
summary
That’s all I need to know about Promise/A+. It mainly covers the usage and precautions of terms and Then methods. Of particular interest is the processing of the parameter return value in the THEN method. Next, we implement promises in TypeScript on top of the specification.
Next, the specification that appears in the article refers specifically toPromise/A+
specification
Promise to realize
Promise itself is a constructor that can be implemented as a class. Next, we’ll focus on implementing a Promise class.
Let’s take a look at the properties and methods that the next standard Promise object has to get some ideas.
Apis provided by Promise:
Promise internal attributes include:
Let’s start with the formal implementation
Declaration file
Before we start, let’s take a look at some of the type declarations involved in writing promises in TypeScript. You can look at this declaration file.
Mainly include:
Declaration files for external modules are a core part of TypeScript. In addition, a declaration file gives you a general idea of the API exposed by your module (what types it accepts, or what types of data it returns). Designing the API in advance is a good development habit, but it can be difficult in practice.
Next, the constructor, the beginning of the core implementation of the Promise class.
The constructor
The specification mentions that the Promise constructor takes as its first argument a function of type Resolver that takes resolve and reject to handle the Promise state.
The implementation is as follows:
class Promise {
// Internal attributes
private ['[[PromiseStatus]]']: PromiseStatus = 'pending';
private ['[[PromiseValue]]'] :any = undefined;
subscribes: any[] = [];
constructor(resolver: Resolver<R>) {
this[PROMISE_ID] = id++;
Resolver must be a function
typeofresolver ! = ='function' && resolverError();
// To use the Promise constructor, use the new operator
this instanceof Promise ? this.init(resolver) : constructorError();
}
private init(resolver: Resolver<R>) {
try {
// Pass in two parameters and get the final value or rejection factor passed in by the user.
resolver(
value= > {
this.mockResolve(value);
},
reason= > {
this.mockReject(reason); }); }catch (e) {
this.mockReject(e);
}
return null;
}
private mockResolve() {
// TODO
}
private mockReject() {
// TODO}}Copy the code
[[Resolve]]
From the previous specification section, we learned that [[Resolve]] is an internal implementation that handles the return value of the THEN parameter. That’s the method you’ll implement here called mockResolve.
As you can see from the specification, the mockResolve method may accept values as Promises, thenable, and other valid JavaScript values.
private mockResolve(value: any) {
// The specification says that resolve cannot pass a promise that is currently returned
[Resolve]](Resolve,x) ' == x
if (value === this) {
this.mockReject(resolveSelfError);
return;
}
// Non-objects and functions are handled directly
if(! isObjectORFunction(value)) {this.fulfill(value);
return;
}
// Handle some objects or functions like Promise, namely thenable
this.handleLikeThenable(value, this.getThen(value));
}
Copy the code
Process the Thenable object
Focus on the handleLikeThenable implementation, which can be analyzed in combination with several cases mentioned in the previous specification:
private handleLikeThenable(value: any, then: any) {
// Handle "real" Promise objects
if (this.isThenable(value, then)) {
this.handleOwnThenable(value);
return;
}
// Fail to get the THEN value and throw an exception as a reject PROMISE
if (then === TRY_CATCH_ERROR) {
this.mockReject(TRY_CATCH_ERROR.error);
TRY_CATCH_ERROR.error = null;
return;
}
// Check the validity of the then method if then is a function
if (isFunction(then)) {
this.handleForeignThenable(value, then);
return;
}
// If no Thenable is adopted, the final plant is directly assigned to fulfill
this.fulfill(value);
}
Copy the code
Handle Thenable where Then is a function
The specification mentions:
If then is a function, x is called as the function’s scope this. Pass two callback functions as arguments, the first called resolvePromise and the second called rejectPromise.
At this point, handleForeignThenable is used to verify the THEN method.
The implementation is as follows:
private tryThen(then, thenable, resolvePromise, rejectPromise) {
try {
then.call(thenable, resolvePromise, rejectPromise);
} catch (e) {
returne; }}private handleForeignThenable(thenable: any, then: any) {
this.asap((a)= > {
// If both resolvePromise and rejectPromise are invoked,
// Or if the same argument is called more than once, the first call takes precedence and the rest are ignored
// Sealed (sealed or not
let sealed = false;
const error = this.tryThen(
then,
thenable,
value= > {
if (sealed) {
return;
}
sealed = true;
if(thenable ! == value) {this.mockResolve(value);
} else {
this.fulfill(value); }},reason= > {
if (sealed) {
return;
}
sealed = true;
this.mockReject(reason); });if(! sealed && error) { sealed =true;
this.mockReject(error); }}); }Copy the code
Fulfill implementation
Take the last step in [[Resolve]], fulfill:
private fulfill(value: any) {
this['[[PromiseStatus]]'] = 'fulfilled';
this['[[PromiseValue]]'] = value;
// Used to handle asynchronous cases
if (this.subscribes.length ! = =0) {
this.asap(this.publish); }}Copy the code
Looking at this, you may have noticed that many methods have private modifiers in TypeScript
A property or method modified by private is private and cannot be accessed outside the class in which it is declared
As mentioned in the spec, the PromiseStatus property cannot be changed externally, meaning that the promise state can only be changed once, and only from within, which is the responsibility of the private method FULFILL.
[[Resolve]]
summary
At this point, an internal [[Resolve]] is implemented. To review, [[Resolve]] is used to handle the following cases
// Instantiate the constructor, passing in the case of resolve
const promise = Promise(resolve= > {
const value: any;
resolve(value);
});
Copy the code
As well as
// then method returns a value
promise.then(
(a)= > {
const value: any;
return value;
},
(a)= > {
const reason: any;
returnreason; });Copy the code
There are many cases for the final value. When handling Thenable, please refer to the specification for implementation. In addition to resolve, there is reject, which is relatively simple and will be explained later. Let’s start with the then method implementation that is closely associated with Resolve. This is also the core approach of Promises.
Then method implementation
From the previous implementation, we can change the internal [[PromiseStatus]] state and the internal [[PromiseValue]] value from the Promise constructor, and make compatibility for various values. Next, it’s time to hand these values over to the first parameter onFulfilled in the THEN method.
Let’s take a look at this before we explain:
promise2 = promise1.then(onFulfilled, onRejected);
Copy the code
When the then method of promise1 is used, a promise object is returned as follows:
class Promise{ then(onFulfilled? , onRejected?) {// Corresponds to promise1 as described above
const parent: any = this;
// corresponding to promise2 as described above
const child = new parent.constructor((a)= > {});
// Select the processing based on the state of the promise
const state = PROMISE_STATUS[this['[[PromiseStatus]]']].if (state) {
// Promise states correspond to 'pending' (0), 'depressing' (1), and 'Rejected' (2)
const callback = arguments[state - 1];
this.asap((a)= >
this.invokeCallback(
this['[[PromiseStatus]]'],
child,
callback,
this['[[PromiseValue]]'])); }else {
// The processing logic that calls the THEN method's promise in a pending state, usually asynchronous.
this.subscribe(parent, child, onFulfilled, onRejected);
}
// Return a Promise object
returnchild; }}Copy the code
The one that’s a little bit more dramatic here is ASAP which we’ll talk about separately. The then method accepts two parameters. The onFulfilled or onRejected method is called according to the current promise state.
Now you’ll be interested in how the code in the then method is executed, such as the following console.log:
promise.then(value= > {
console.log(value);
});
Copy the code
Next, look at the invokeCallback method related to this
Then method callback processing
Ondepressing and onRejected in the then method are both optional parameters. Before further explanation, it is suggested that we first understand the characteristics of the two parameters mentioned in the specification.
Now let’s explain the parameters accepted by invokeCallback and what they mean:
-
When a promise is settled in a non-pending state, it is settled. The value can be fulfilled or rejected
-
Child The promise object that will be returned
-
Callback Selects the ondepressing or onRejected callback function based on Texas parameters
-
Detail The value or reason of the then method promise that is currently called
Notice heresettled
和 detail
.settled
Used to refer tofulfilled
或 rejected
Detail is used to refer tovalue
或 reason
It all makes sense
Knowing this, you just need to refer to the way the specification suggests implementing it:
private invokeCallback(settled, child, callback, detail) {
// check whether there is a corresponding logical processing of callback
// Whether the callback function will throw an exception after execution, that is, processing accordingly
// return value cannot be handled by its own logic
// 4, the logic processing of the callback cannot be executed until the promise ends
// ...
}
Copy the code
Need to deal with the logic has been given, the rest of the way readers can achieve or see the project source code implementation. It is recommended that all implementations should refer to the specification for implementation, there may be omissions or error handling in the implementation process. (PS: Time to test the robustness of a dependency)
So far, you’ve dealt with synchronization. Promise was the master of asynchrony, so there was no shortage of implementations. There are callback and subscribe publish modes to handle asynchrony, and we implemented Promise to solve callback hell, so subscribe publish is of course the way to go here.
Then asynchronous processing
The case to deal with here is when the promise calling the THEN method is in a pending state.
So when does that happen? Take a look at this code:
const promise = new Promise(resolve= > {
setTimeout((a)= > {
resolve(1);
}, 1000);
});
promise.then(value= > {
console.log(value);
});
Copy the code
I’m going to write the code here, if this happens. Our promise didn’t actually work. Since setTimeout is an exception operation, resolve is not executed at all when the internal THEN method executes synchronously, i.e. [[PromiseStatus]] of the then promise is currently ‘pending’. [[PromiseValue]] is undefined when added to the pending state of the callback is doesn’t make any sense, other specifications mentioned then callback methods must be in the hills (have talked about before) will call the callback accordingly.
Or we don’t have to worry about asynchrony, we just need to be clear about one thing. There is a case where the promise state of a call to the THEN method might be pending.
There must be a mechanism to handle this. The code implementation is:
private subscribe(parent, child, onFulfillment, onRejection) {
let {
subscribes,
subscribes: { length }
} = parent;
subscribes[length] = child;
subscribes[length + PROMISE_STATUS.fulfilled] = onFulfillment;
subscribes[length + PROMISE_STATUS.rejected] = onRejection;
if (length === 0 && PROMISE_STATUS[parent['[[PromiseStatus]]']]) {
this.asap(this.publish); }}Copy the code
Subscribe to accept four parameters the parent, child, onFulfillment, onRejection
parent
Is the promise object that currently calls the then methodchild
Is the promise object to be returned by the THEN methodonFulfillment
The first argument to the then methodonFulfillment
The second argument to the then method
An array is used to store subscribe, which mainly holds the return Promise object and the corresponding onFulfillment and onRejection callback functions.
Subscribe is a new condition and publish is called if the value of [[PromiseStatus]] of the then promise is not ‘pending’. That is, in asynchronous cases, the publish method is not called. So publish is a method associated with performing a callback.
So in the asynchronous case, when does that trigger a callback? Recall the FULFILL method explained earlier:
private fulfill(value: any) {
this['[[PromiseStatus]]'] = 'fulfilled';
this['[[PromiseValue]]'] = value;
// Used to handle asynchronous cases
if (this.subscribes.length ! = =0) {
this.asap(this.publish); }}Copy the code
When this.subscribes. Length! Publish is triggered when == 0. That is to say, when the resolve method is called after the asynchronous function is executed, there will be a judgment whether to call the callback function in the Subscribes.
This ensures that the callback in the THEN method will only fire after the asynchronous function has finished executing. Next, take a look at the associated publish method
The publish method
First, publish is publish and calls the callback through invokeCallback. In this project, only subscribes is involved. Let’s get straight to the code:
private publish() {
const subscribes = this.subscribes;
const state = this['[[PromiseStatus]]'];
const settled = PROMISE_STATUS[state];
const result = this['[[PromiseValue]]'];
if (subscribes.length === 0) {
return;
}
for (let i = 0; i < subscribes.length; i += 3) {
// The promise object to return
const item = subscribes[i];
const callback = subscribes[i + settled];
if (item) {
this.invokeCallback(state, item, callback, result);
} else{ callback(result); }}this.subscribes.length = 0;
}
Copy the code
Then method summary
At this point we have implemented the then method in promises, which means that the promises implemented so far are capable of handling asynchronous data flows. The implementation of THEN methods cannot be separated from the guidance of the specification. As long as you refer to the description of THEN methods in the specification, the rest is just logical processing.
This completes the core promise functionality, which is the internal [[Resolve]] and then methods. Let’s take a quick look at the rest of the API.
Syntax sugar API implementation
Catch and finally are grammatical sugars
-
Catch belongs to this.then(null, onRejection)
-
Finally belongs to this.then(callback, callback);
The promise also provides static methods for resolve, Reject, All, and Race, all of which return a new Promise object for the chained invocation.
Resolve
reject
Reject is slightly different from resolve in that it does not handle thenable. Reject is used as a reject value. Reason is recommended as error.
private mockReject(reason: any) {
this['[[PromiseStatus]]'] = 'rejected';
this['[[PromiseValue]]'] = reason;
this.asap(this.publish);
}
static reject(reason: any) {
let Constructor = this;
let promise = new Constructor((a)= > {});
promise.mockReject(reason);
return promise;
}
private mockReject(reason: any) {
this['[[PromiseStatus]]'] = 'rejected';
this['[[PromiseValue]]'] = reason;
this.asap(this.publish);
}
Copy the code
all & race
It is not difficult to extend all and Race from the previous API. Here’s how both work:
-
All is used to process a set of promises, and returns a reason corresponding to an array of values or reject when all promises are resolved or a promise Reject is satisfied
-
A race is a set of promises that are used to see who performs faster
Corresponding implementation code:
// all
let result = [];
let num = 0;
return new this((resolve, reject) = > {
entries.forEach(item= > {
this.resolve(item).then(data= > {
result.push(data);
num++;
if (num === entries.length) {
resolve(result);
}
}, reject);
});
});
// race
return new this((resolve, reject) = > {
let length = entries.length;
for (let i = 0; i < length; i++) {
this.resolve(entries[i]).then(resolve, reject); }});Copy the code
timing
If there are asynchronous functions that need to be executed in sequence, you can use the following methods:
[func1, func2].reduce((p, f) = > p.then(f), Promise.resolve());
Copy the code
In ES7 timing composition can be achieved by using async/await
for (let f of [func1, func2]) {
await f();
}
Copy the code
See this article for more information on how to use it
Knowledge supplement
Promise was invented to better handle asynchronous data flow, or callback hell as it’s often called. Callbacks are used in asynchronous cases. If they are not asynchronous, callbacks are generally not needed. Here’s a look at some of the concepts that come up in implementing promises:
-
The callback function
-
Asynchronous & Synchronous
-
EventLoop
-
asap
The callback function
A callback function is a callback function.
A callback is a function that is passed as an argument to another function and is executed after its parent function has Completed.
Literally, a callback is a function passed as an argument to another function, and when that function is finished executing, the function passed in is executed. This process is called a callback.
In JavaScript, A callback is defined as if function A is passed as an argument (function reference) to another function B that executes function A. Let’s say function A is called the callback function. If there is no name (function expression), it is called an anonymous callback function.
The first thing to note is that the callback function is just an implementation, not an asynchronous pattern-specific implementation. Callbacks can also be used in synchronous (blocking) scenarios and other scenarios.
Callback functions need to be distinguished from asynchronous functions.
Asynchronous functions & synchronous functions
-
If the caller gets the expected result when the function returns, it is a synchronous function.
-
A function is asynchronous if the caller does not get the expected result when it returns, but needs to get it in the future by some means.
So what is asynchrony? Synchronization?
Asynchronous & Synchronous
First, make it clear that the Javascript language is executed in a “single thread” environment. Single-threaded means that you can only complete one task at a time. If there are multiple tasks, they must be queued, the first task completed, the next task executed, and so on.
This pattern creates a blocking problem, and to address this, the Javascript language divides the execution mode of the task into two modes: Synchronous and Asynchronous.
However, it is important to note that asynchrony is done by two or more resident threads of the browser, while Javascript single-threading and asynchrony are more of a behavior of the browser. In other words, Javascript itself is single-threaded and has no asynchronous nature.
As the application scenario of Javascript is browser, the browser itself is a typical GUI worker thread. GUI worker thread is implemented as event processing in most systems to avoid blocking interaction, thus generating Javascript asynchronous gene. All methods and functions that involve asynchrony are executed by another thread in the browser.
Threads in the browser
-
Browser event-triggering thread When an event is triggered, the thread adds the event to the end of the queue waiting for the JavaScript engine to process it. These events can be currently executing code blocks, such as timed tasks, or other threads from the browser kernel, such as mouse clicks, AJAX asynchronous requests, etc., but because JavaScript is single-threaded, all of these events have to be queued up for the JavaScript engine to process.
-
Timing trigger thread Browser timing counters are not counted by JavaScript engine, because JavaScript engine is single-threaded, if the blocking thread state will affect the accuracy of timing, so it is more reasonable to use a separate thread to time and trigger timing.
-
Asynchronous HTTP request thread XMLHttpRequest after the connection is through the browser to open a new thread request, will detect the state change, if the callback function is set, the asynchronous thread will generate the state change event into the JavaScript engine processing queue waiting for processing;
From the above, you can see that JavaScript is asynchronous through the interaction between the JS engine thread and other threads in the browser.
But when exactly is the callback added to the JS engine thread? What is the order of execution?
Let’s take a look at the EventLoop mechanism associated with this
EventLoop
Stack, Heap, Queue
-
Operations that do not require a callback function fall into the Stack category
-
Heap is used to store declared variables, objects, etc
-
Once an asynchronous task has a response, it is pushed to the Queue
A rough process is as follows:
The JS engine thread is used to execute the synchronization task in the stack. When all synchronization tasks are completed, the stack is emptied, and then a pending task in the message queue is read, and the related callback function is pushed onto the stack. The single thread starts to execute the new synchronization task.
The JS engine thread reads tasks from the message queue in a continuous loop. Every time the stack is cleared, it will read new tasks in the message queue. If there is no new task, it will wait until there is a new task, which is called the event loop.
I don’t know who drew this picture, it’s really great! Let’s borrow a general description of AJAX flow:
AJAX requests are very time-consuming asynchronous operations, and browsers have dedicated threads to handle them. Asynchronous tasks are triggered when there is code calling AJAX on the main thread. The job of executing the asynchronous task is left to the AJAX thread, and the main thread does not wait for the result of the asynchronous operation but continues to execute. Let’s say the main thread finishes executing at some point, so the Stack is empty. In the earlier days, the asynchronous task would have placed the message in the Queue after execution, so that a callback function could be taken from it to execute if the Stack was empty.
This is called EventLoop, for a general idea. Take a look at EventLoop again:
The backer of asynchrony is Event Loops. Asynchron is actually called event Loops for browsers or javaScript environments because there are no Event Loops in ECMAScript. Event Loops are defined in HTML Standard.
Event loop is translated as an event loop, which can be understood as a way to achieve asynchrony. Let’s take a look at the definition section of event loop in HTML Standard:
To coordinate events, user interactions, scripting, rendering, networking, etc., the user agent must use the Event loop described in this section.
Events, user interaction, scripting, rendering, networking these are all familiar things that are coordinated by the Event Loop. Trigger a click event, make an Ajax request, and have an Event loop running behind it.
task
An Event loop has one or more task queues. Each task comes from a specific task source, such as a task queue for mouse and keyboard events, and a separate queue for other events. You can allocate more time for mouse and keyboard events to ensure smooth interaction.
A task queue, also known as a macrotask, is better understood as a first-in, first-out queue in which a task is provided by a specified task source.
Task:
- setTimeout
- setInterval
- setImmediate
- I/O
- UI rendering
microtask
Each event loop has a microtask queue, and a microtask is placed in a microTask queue rather than a task queue. A MicroTask queue is similar to a task queue in that it is a first-in, first-out queue and the task is provided by the specified task source. The difference is that there is only one MicroTask queue in an Event loop.
Microtask tasks are generally considered to have the following sources:
- process.nextTick
- promises
- Object.observe
- MutationObserver
An interview question about EventLoop
console.log('start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve()
.then(function() {
console.log('promise1');
})
.then(function() {
console.log('promise2');
});
console.log('end');
// start
// end
// promise1
// promise2
// setTimeout
Copy the code
The above sequence is run on Chrome, interestingly tested in Safari 9.1.2, promise1 promise2 is after setTimeout, and safari 10.0.1 yields the same result as Chrome. The difference between browsers is that some browsers put THEN in the Macro-Task queue, while others put it in the micro-task queue.
EventLoop summary
There are many things involved in event loop, but this article only mentions the knowledge points that may be related to promise for fear of deviation. If you want to know more about the students, it is recommended to read this article will benefit a lot.
What is asap
As soon as possible is an abbreviation for promise to respond to changes as soon as possible.
In the Notes 3.1 Promises/A + specification mentioned promise then method can be used in A “macro (macro – task) task” mechanism or “micro task (micro – task)” mechanism to realize.
Macro – Task mechanism is adopted in this project
private asap(callback) {
setTimeout((a)= > {
callback.call(this);
}, 1);
}
Copy the code
Or the MutationObserver pattern:
function flush() {... }function useMutationObserver() {
var iterations = 0;
var observer = new MutationObserver(flush);
var node = document.createTextNode(' ');
observer.observe(node, { characterData: true });
return function () {
node.data = iterations = ++iterations % 2;
};
}
Copy the code
The MutationObserver function is used to observe changes in the DOM. What’s the point of creating a node out of thin air to repeatedly modify its contents and trigger a callback for observation?
The answer is that using Mutation events allows the operation (the flush function in the example) to be performed asynchronously, either to respond to changes as quickly as possible, or to remove duplicate calculations.
Or node:
function useNextTick() {
return (a)= > process.nextTick(flush);
}
Copy the code
Front-end security
Now the front-end is in the heyday of modular development, in the face of NPM that thousands of “designer bags”, many white people like me will inevitably lose themselves, gradually no sense of security.
Do you want to be a copyist, or do you want to study the underlying principles occasionally like I did, look at other people’s code, and masturbate yourself to get a sense of what’s going on and improve your security?
Do front end also has paragraph of time, be buried by business those years, really sigh! Recall once almost went to do the design of I finally want to enter the front end, the heart unavoidably want to rub, then write this!
conclusion
I’m tired of writing too much. Hope to help you.
The source code
reference
Promises/A+ MDN Promises Promises use Event loop