Original: www.valentinog.com/blog/engine…

Translator: Front-end wisdom

Click “like” and then look, wechat search [Big Move the world] pay attention to this person without dACHang background, but with a positive attitude upward. In this paper, making github.com/qq449245884… Has been included, the article has been categorized, also organized a lot of my documentation, and tutorial materials.

Everyone said there was no project on your resume, so I found one and gave it away【 Construction tutorial 】.

Ever wonder how browsers read and run JS code? This looks amazing, and we can see some of the principles behind it by looking at the console provided by the browser.

Open the Browser console in Chrome and look at the Sources bar, which brings you to a Call Stack box on the right.

The JS engine is a powerful component that compiles and interprets our JS code. The most popular JS engines are V8, used by Google Chrome and Node. JS, SpiderMonkey for Firefox, and JavaScriptCore for Safari/WebKit.

Although now the JS engine is not helping us deal with the overall work. But within each engine there are smaller components that do the tedious work for us.

One of these components is the Call Stack, which runs our code along with global memory and execution context.

Js engine and Global Memory

JavaScript is both a compiled language and an interpreted language. Believe it or not, the JS engine only needs a few microseconds to compile the code before it executes it.

That sounds amazing, right? This magical feature is called JIT(Just-in-time compilation). This is such a big topic that even one book can’t describe how JIT works. But for now, we can skip the theory behind compilation for lunch and focus on the execution phase, which is nonetheless interesting.

Consider the following code:

var num = 2;
function pow(num) {
    return num * num;
}
Copy the code

How do you handle this code in a browser? What would you say? You might say “browser reads code” or “browser executes code.”

The reality is more nuanced than that. First, it’s not the browser that reads this code, it’s the JS engine. The JS engine reads the code and, as soon as the first line is encountered, puts several references into global memory.

Global memory (also known as heap) where the JS engine holds variable and function declarations. So, going back to the example above, when the JS engine reads the code above, there are two bindings in global memory.

Even if the examples are only variables and functions, consider that your JS code runs in a larger context: in a browser or in Node.js. In these environments, there are many predefined functions and variables called global variables. Global memory will be more than NUM and POW.

In the above example, nothing is done, but what if we run the function like this:

var num = 2;
function pow(num) {
    return num * num;
}
pow(num);
Copy the code

Now things get interesting. When a function is called, the JavaScript engine makes room for the global execution context and call stack.

JS engines: How do they work? Global execution context and call stack

You just saw how the JS engine reads variable and function declarations, which are eventually put into global memory (the heap).

But now that we’ve executed a JS function, the JS engine must handle it. How to do? Every JS engine has a basic component called the call stack.

The call stack is a stack data structure: this means that elements can come in from the top, but if they have some elements on top of them, they can’t leave, which is how JS functions work.

Once executed, other functions cannot leave the call stack if they are still blocked. Note that this will help you understand the statement that JavaScript is single threaded.

Returning to our example, when a function is called, the JS engine pushes the function onto the call stack

At the same time, the JS engine also assigns a global execution context, which is the global environment to run the JS code, as shown below

Imagine a global execution context as an ocean in which global functions swim like fish. But the reality is far from simple. What if my function has some nested variables or one or more internal functions?

Even with simple changes like the following, the JS engine creates a local execution context:

var num = 2;
function pow(num) {
    var fixed = 89;
    return num * num;
}
pow(num);
Copy the code

Notice that I have added a variable named Fixed to the POw function. In this case, a local execution context is created in the POW function, and fixed variables are placed in the local execution context in the POW function.

For each nested function of a nested function, the engine creates more local execution contexts.

JavaScript is single threaded and other interesting stories

JavaScript is single-threaded because there is only one call stack to process our function. That is, a function cannot leave the call stack if there are other functions waiting to be executed.

This is not a problem when dealing with synchronized code. For example, the sum between two numbers is synchronous and measured in microseconds. But what about asynchrony?

Fortunately, JS engines are asynchronous by default. Even if it executes one function at a time, there is a way to make external functions (e.g., browsers) execute slower functions, a topic I’ll explore later.

When the browser loads some JS code, the JS engine reads it line by line and performs the following steps:

  • Put declarations of variables and functions into global memory (heap)
  • Puts the call to the function on the call stack
  • Create a global execution context in which global functions are executed
  • Create multiple local execution contexts (if there are internal variables or nested functions)

So far, the JS engine synchronization mechanism has a basic understanding. In the next section, we will talk about how JS works asynchronously.

Asynchronous JS, callback queues and event loops

The global memory (heap), execution context, and call stack explain how synchronous JS code works in the browser. However, we are missing something. What happens when there are some asynchronous functions running?

Remember that the call stack can execute one function at a time, and even a blocking function can freeze the browser directly. Fortunately, JavaScript engines are smart and can solve problems with the help of browsers.

When we run an asynchronous function, the browser accepts the function and runs it. Consider the following code:

setTimeout(callback, 10000); function callback(){ console.log('hello timer! '); }Copy the code

SetTimeout is known to be used many times, but you may not know that it is not a built-in JS function. That is, when JS comes along, there is no built-in setTimeout in the language.

Part of the setTimeout Browser API(Browser API), which is a convenient set of tools that browsers give us for free. What does this mean in practice? Since setTimeout is an Api for a browser, the function is run directly by the browser (it appears in the call stack for a while, but is immediately removed).

After 10 seconds, the browser accepts the Callback function we passed and moves it to the Callback Queu. Consider the following code

var num = 2; function pow(num) { return num * num; } pow(num); setTimeout(callback, 10000); function callback(){ console.log('hello timer! '); }Copy the code

The schematic diagram is as follows:

As you can see, setTimeout runs in the browser context. After 10 seconds, the timer is triggered and the callback is ready to run. But first it must pass through the Callback Queue. A callback queue is a queue data structure, and a callback queue is an ordered queue of functions.

Each asynchronous function must pass through the callback queue before it is placed on the call stack, but who does the job? The Event Loop.

The event loop has only one job: it checks to see if the call stack is empty. If there is a function in the Callback Queue and the call stack is free, it is put in the call stack.

When done, execute the function. Here is a diagram of the JS engine for handling asynchronous and synchronous code:

Imagine that the callback() is ready to execute, and when the POW () completes, the Call Stack is empty, and the Event Look drops the callback() into the Call heap. That’s about it, if you understand the illustration above, then you can understand all JavaScript.

Back to Promises in Hell and ES6

Callback functions are ubiquitous in JS and are used for both synchronous and asynchronous code. Consider the following map method:

function mapper(element){
    return element * 2;
}
[1, 2, 3, 4, 5].map(mapper);
Copy the code

Mapper is a callback function passed inside a map. The above code is synchronous, consider the asynchronous case:

function runMeEvery(){ console.log('Ran! '); } setInterval(runMeEvery, 5000);Copy the code

This code is asynchronous, and we pass the callback runMeEvery in setInterval. Callbacks are so ubiquitous in JS that there is a problem: callback hell.

Callback hell in JavaScript refers to a programming style in which callbacks are nested within callback functions, which in turn are nested within other callback functions. JS programmers have fallen into this trap for years due to the asynchronous nature of JS.

To be honest, I’ve never encountered an extreme callback pyramid, probably because I value readable code and I always stick to it. If you run into callback hell again, your function is doing too much.

Callback hell won’t be discussed here, but if you’re curious, there’s a website, Callbackhell.com, that explores the problem in more detail and offers some solutions.

We’re looking at Promises for ES6. ES6 Promises is a complement to the JS language designed to solve the dreaded callback hell. But what are Promises?

JS promises are representations of future events. Promises can be fulfilled with success: This is a big pity, with the jargon saying we’ve resolved. But if a Promise fails, we say it is in the Rejected state. Promises also have a default state: each new Promise starts in a pending ** state.

Create and use JavaScript Promises

To create a new Promise, call the Promise constructor by passing the callback function. Callbacks can take two arguments :resolve and reject. As follows:

const myPromise = new Promise(function(resolve){
    setTimeout(function(){
        resolve()
    }, 5000)
});
Copy the code

As shown below, resolve is a function that is called to make a Promise succeed, as well as reject to indicate a call failure.

const myPromise = new Promise(function(resolve, reject){
    setTimeout(function(){
        reject()
    }, 5000)
});
Copy the code

Note that reject can be omitted in the first example because it is the second argument. However, if you’re going to use Reject, you can’t ignore Resolve, as shown below, and you’ll end up with a Resolved commitment, not reject.

// Resolve! const myPromise = new Promise(function(reject){ setTimeout(function(){ reject() }, 5000) });Copy the code

Right now, Promises don’t look very useful. We can add some data to Promises, like this:

const myPromise = new Promise(function(resolve) {
  resolve([{ name: "Chris" }]);
});
Copy the code

But we still don’t see any data. To extract data from a Promise, you link to a method called THEN. It requires a callback to receive the actual data:

const myPromise = new Promise(function(resolve, reject) {
  resolve([{ name: "Chris" }]);
});
myPromise.then(function(data) {
    console.log(data);
});
Copy the code

Wrong handling of Promises

For synchronous code, JS error handling is mostly simple, as follows:

function makeAnError() { throw Error("Sorry mate!" ); } try { makeAnError(); } catch (error) { console.log("Catching the error! " + error); }Copy the code

Will print:

Catching the error! Error: Sorry mate!
Copy the code

Now try using an asynchronous function:

function makeAnError() { throw Error("Sorry mate!" ); } try { setTimeout(makeAnError, 5000); } catch (error) { console.log("Catching the error! " + error);Copy the code

Because of setTimeout, the above code is asynchronous. See what happens when you run it:

throw Error("Sorry mate!" ); ^ Error: Sorry mate! at Timeout.makeAnError [as _onTimeout] (/home/valentino/Code/piccolo-javascript/async.js:2:9)Copy the code

The output is different this time. The error does not pass through the catch block, which is free to travel up the stack.

That’s because try/catch only works with synchronized code. Error handling in Node.js explains this in more detail, if you’re curious.

Fortunately, promises have a way of handling asynchronous errors as if they were synchronous:

const myPromise = new Promise(function(resolve, reject) { reject('Errored, sorry! '); });Copy the code

In the example above, we can use the catch handler to handle the error:

const myPromise = new Promise(function(resolve, reject) { reject('Errored, sorry! '); }); myPromise.catch(err => console.log(err));Copy the code

We can also call promise.reject () to create and reject a Promise

Promise.reject({msg: 'Rejected! '}).catch(err => console.log(err));Copy the code

Promise. All, Promise. AllSettled, Promise

The Promise API provides a number of ways to group promises together. The most useful of these is promise.all, which accepts a Promises array and returns a Promise. If a promise fails (Rejected), the instance calls back (reject) because of the result of the first failed promise.

The promise.race (iterable) method returns a Promise that is resolved or rejected once a Promise in the iterator is resolved or rejected.

Newer versions of V8 will also implement two new combinations: Promise.allSettled and Promise.any. Promise.any is still in the early stages of the proposal: as of this writing, there is still no browser support for it.

Promise.any indicates whether any Promise is fullfilled. Race differs from promise. race in that promise. any does not reject even if one of the promises is rejected.

In any case, the most interesting of the two is Promise.AllSettled, which is also an array of promises, but doesn’t short-circuit if one of the promises rejects. It’s useful when you want to check that the Promise array is all resolved, whether it’s ultimately rejected or not. Think of it as an opponent of promise.all.

Async /await: Promises to Async /await

With the advent of ECMAScript 2017 (ES8), a new syntax is born async/await.

Async /await is just a Promise syntactic candy. It’s just a new way to write asynchronous code based on Promises, async/await does not change JS in any way, remember that JS must be backward compatible with older browsers and should not break existing code.

Here’s an example:

const myPromise = new Promise(function(resolve, reject) {
  resolve([{ name: "Chris" }]);
});
myPromise.then((data) => console.log(data))
Copy the code

With async/await, we can wrap a Promise in a function labeled async and wait for the result to return:

const myPromise = new Promise(function(resolve, reject) {
  resolve([{ name: "Chris" }]);
});
async function getData() {
  const data = await myPromise;
  console.log(data);
}
getData();
Copy the code

Interestingly, async also returns promises, which you can do as well:

async function getData() {
  const data = await myPromise;
  return data;
}
getData().then(data => console.log(data));
Copy the code

So how do you handle errors? An advantage of async/await is that you can use a try/catch. Looking at promises, we use a catch handler to handle errors:

const myPromise = new Promise(function(resolve, reject) { reject('Errored, sorry! '); }); myPromise.catch(err => console.log(err));Copy the code

Using the async function, we can refactor the above code:

async function getData() {
  try {
    const data = await myPromise;
    console.log(data);
    // or return the data with return data
  } catch (error) {
    console.log(error);
  }
}
getData();
Copy the code

Not everyone likes the style. Try /catch makes code verbose, but there’s another quirk to point out when using a try/catch, like this:

async function getData() { try { if (true) { throw Error("Catch me if you can"); } } catch (err) { console.log(err.message); } } getData() .then(() => console.log("I will run no matter what!" )) .catch(() => console.log("Catching err"));Copy the code

Running results:

Both strings are printed. Remember that try/catch is a synchronous construct, but our asynchronous function produces a Promise. They travel on two different tracks, like two trains. But they never meet, that is, an error thrown by the throw never triggers the catch method of **getData()**.

In practice, we don’t want the throw to touch the then handler. One solution is to return promise.reject () from the function:

async function getData() { try { if (true) { return Promise.reject("Catch me if you can"); } } catch (err) { console.log(err.message); }}Copy the code

Now handle the error as expected

getData() .then(() => console.log("I will NOT run no matter what!" )) .catch(() => console.log("Catching err")); "Catching Err "// outputCopy the code

Beyond that, async/await seems to be the best way to build asynchronous code in JS. We have better control over error handling, and the code looks cleaner.

The bugs that may exist after code deployment cannot be known in real time. In order to solve these bugs, I spent a lot of time on log debugging. Incidentally, I recommend a good BUG monitoring tool for youFundebug.

conclusion

JS is a scripting language for the Web with features that are compiled and then interpreted by the engine. Among the most popular JS engines are V8 for Google Chrome and Node. JS, SpiderMonkey for Firefox, and JavaScriptCore for Safari.

The JS engine contains many components: call stack, global memory (heap), event loop, callback queue. All of these components work together, perfectly tuned to handle both synchronous and asynchronous code in JS.

The JS engine is single-threaded, which means there is only one call stack to run functions. This limitation is fundamental to the asynchronous nature of JS: all operations that take time must be taken care of by external entities (such as browsers) or callback functions.

To simplify asynchronous code flow, ECMAScript 2015 brings Promise. Promise is an asynchronous object that represents the failure or success of any asynchronous operation. But the improvements don’t stop there. In 2017, async/ await was born: it was a stylistic complement to Promise, making it possible to write asynchronous code as if it were synchronous.

communication

This article is updated every week, you can search wechat “big move the world” for the first time to read and urge more (one or two earlier than the blog hey), this article GitHub github.com/qq449245884… It has been included and sorted out a lot of my documents. Welcome Star and perfect. You can refer to the examination points for review in the interview.