1 Synchronous and asynchronous

1.1 JS can only do one thing at a time

First, we need to understand that JS is a single-threaded language that can only do one thing at a time.

We can understand this principle by considering that JS execution and DOM rendering share a single thread:

  • When JS modifies the DOM, the browser does not render the DOM, that is, dom rendering is blocked

And this blocked behavior, we call it synchronization

1.2 Concepts of synchronization and asynchronism

synchronous

  • English:Synchronization, usually abbreviated toSync
  • Definition: synchronous behavior corresponds to sequential execution of processor instructions in memory. If you don’t understand this, you mean sequential execution of JS code.
  • The code must wait for the result before it can proceed.

For example, in the following example, the recursive function blocks the execution of the last sentence, so opening the console will reveal that 2 is not printed immediately

function wait() {
    let start = new Date(a);while (new Date() - start < 4000) { // block for 4 seconds
        
    }
    console.log(2);
}

// Core logic
console.log(1)
wait();
console.log(3); // The execution of this sentence was blocked for 4 seconds

// Input results: 1, 2, 3
Copy the code

asynchronous

  • English:Asynchronization, usually abbreviated toAsync
  • The code doesn't have to wait for the result to come out.

What do you mean? Give me an example

function wait() {
	setTimeout(() = > console.log(2), 4000)}// Core logic
console.log(1);
wait(); // After 4 seconds, get the result
console.log(3); // This would have taken 4 seconds without asynchrony, so thank asynchrony

// Enter the result: 1 3 2
Copy the code

2 Asynchronous applications

2.1 Common Asynchronous Scenarios

1. Network request

// Ajax
console.log(1);
$.get(url1, data1= > {
    console.log(data1);
  	console.log(2);
})
console.log(3);
/ / 1 2 3
Copy the code

2. Scheduled tasks, such as setTimeout and setTimeInterval

console.log(1);
setTimeout(() = > console.log(2), 1000);
console.log(3);
Copy the code
console.log(1);
setInterval(() = > console.log(2), 1000);
console.log(3);
Copy the code

3. Event monitoring

// Load the image
console.log(1);

let img = document.createElement('img');
// Onload is a callback that is triggered as soon as the image is loaded
img.onload = () = > console.log(2);
// after the SRC assignment, the image will start loading
img.src = '/xxx.png';

console.log(3);
/ / 1 2 3
Copy the code

2.2 Common Asynchronous Problems

2.1 Image loading problem

// Condition: the user's browser requests the image for the first time, so the user's browser is not cached
document.getElementsByTagNames('img') [0].width // The width is 0
Copy the code

Why is width 0? Img is not downloaded while JS is running

The solution

let imgNode = document.getElementsByTagName('img') [0]
imgNode.addEventListener('onload'.function () {
  / / callback
  console.log(this.width)
})
Copy the code

2.2 Asynchronous questions often tested in interviews

This problem can also be categorized as a scoping problem

// Suppose there are five Li's
let liList = document.querySelectorAll('li')
for (var i = 0; i < liList.length; i++) {
	liList[i].onclick = function () {
		console.log(i) // 5}}Copy the code

Why is that? Because the onclick event is handled asynchronously, by the time the user fires the onclick event, the loop has already ended

And because of var, I is promoted to be a global variable, so I is 5

So this happens

Solution 1 [Execute function now to create independent scope (not recommended)]

// Suppose there are five Li's
let liList = document.querySelectorAll('li')
for (var i = 0; i < liList.length; i++) { ! (function (j) {
    liList[j].onclick = function () {
      console.log(j) // 1, 2, 3, 4, 5
    }
  })(i)
}
Copy the code

Solution 2 [Using let]

Let makes I a local variable in {} of the for loop

// Suppose there are five Li's
let liList = document.querySelectorAll('li')
for (let i = 0; i < liList.length; i++) {
  liList[i].onclick = function () {
    console.log(i) // 1, 2, 3, 4, 5}}Copy the code

The way to get asynchronous results – callbacks

3.1 What is a callback?

Callback: Pass a function as an argument to another function

function printInfo(info) {
    console.log(info);
}

function getInfo(fn) {
    fn('big woof');
}

PrintInfo is passed as an argument to getInfo
getInfo(printInfo); // select * from *;
Copy the code
  1. PrintInfo isThe callback function
  2. printInfoThe whole point of this function is to be able toGet called by getInfo
  3. Calling printInfo from the getInfo function does the **Trigger the callback function**

3.2 Common callback forms

The error-first form of node.js

Check whether the error exists first. If it does, an error occurs. If it does not, it succeeds

fs.readFile('./1.txt'.(error, content) = > {
  if (error) {
    / / fail
  } else {
    / / success}})Copy the code

JQuery success/error form

$.ajax({
	url: '/xxx'.success: () = >{},error: () = >{}})Copy the code

Done/fail/always in jQuery

$.ajax({
	url: '/xxx',
}).done(() = > {
}).fail(() = > {
}).always(() = >{})Copy the code

There is also the then form of Prosmise, which will be discussed in detail in Section 4

3.3 Nested asynchronous callbacks — Callback hell

In JS, the problem of asynchrony can be solved by callback

The bigger challenge, however, is how to solve the concatenation asynchrony problem. Prior to Promise, the common solution was nested asynchronous callbacks, aka callback hell

Callback hell: Callback set callback set callback set callback set callback set callback set callback set callback set

The following example vividly shows how asynchronous results are handled through callbacks

It just keeps nesting callbacks

/ / data1
$.get(url1, data1= > {
    console.log(data1);
		
    // get data2 after data1
    $.get(url2, data2= > {
        console.log(data2);
        
        // get data2 and then data3
        $.get(url3, data3= > {
            console.log(data3);
						
            / /... It can be repeated indefinitely})})})Copy the code

4 Promise

4.1 Let’s look at an example

You need to know

  1. Axios is a library
  2. Axios () returns an instance of Promise
  3. You can think of axios() as $.ajax(), which has similar functionality, but AXIos follows the Promise specification

For axios documentation, go ahead

axios({
	url: './xxx.json'
}).then((resolve) = > {
	console.log(resolve)
	return 'I am the second then'
}, (reject) = > {
	console.log(reject)
}).then((resolve_2) = > {
	console.log(resolve_2) // 'I am the second then'
}, (reject_2) = > { 
	console.log(reject_2)
})
Copy the code

So just in case you get all dazzled by this chain call, let me simplify this a little bit

axios({
  url: './xxx.json'}). Then (Successful callback1, failed callback1Successful callback2, failed callback2)
Copy the code

I’m sure you’re still confused about the code above, because you don’t promise yet

Let’s take a look at some of the basic concepts of promises

4.2 Basic concepts of Promise

4.2.1 Functions of Promise

Promise was designed specifically to solve asynchronous programming problems, eliminating the need for nested layers of Callback functions (Callback Hell). Here’s an example of asynchronous code written using the traditional method Callback Hell. The Callback Hell approach makes the code very unreadable

let src = 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png'

function loadImg(src, callback, fail) {
    let img = new Image()
    // Successful callback
    img.onload = function () {
        callback(img)
    }
    // Failed callback
    img.onerror = fail
    img.src = src
}

LoadImg (src1, callback 1 on success, callback 1 on failure);
// But on successful callback 1, loadImg(src2, successful callback 2, failed callback 2) again;
loadImg(src1, function (img) {
    console.log(img.width)
    loadImg(src2, function (img) {
        console.log(img.width)
    }, function () {
        console.log('error2')})},function () {
    console.log('error1')})Copy the code

4.2.2 new Promise ()

New Promise(myExecutorFunc) returns an instance of Promise. The myExecutorFunc requirement is a function of the form required:

let myExecutorFunc = (resolve, reject) => {};

See the MDN documentation for details

How does new Promise() get the result of an asynchronous operation

For an asynchronous operation, such as axios({URL :’./xxx.json’}), we want to take a JSON object and pass it out

1. A common mistake many people make is to think that a return will get the result, but this is just a big mistake:

let p = new Promise(() = > {
    let data = 3; // This is the result we want to achieve
    return data; // Return is useless
});
Copy the code

Type P on the console:

2. New Promise() must be passed to myExecutorFunc’s two callbacks, resolve and Reject

let p = new Promise((resolve, reject) = > {
    let data = 3; // This is the result we want to achieve

    resolve(data);
    / / or
    reject(data);
});
Copy the code

Type P on the console:

Of course, this is just to give you a look at the case of a promise instance P. To actually handle the result, you need to use the then/catch and other instance methods, which we’ll talk about later

let p = new Promise((resolve, reject) = > {
    let data = 3; // This is the result we want to achieve

    resolve(data);
    / / or
    reject(data);
});

p.then(data= > {
    console.log(data); / / 3
}, undefined);
Copy the code

4.2.3 Three States of the Promise instance

  1. pendingIs the initial state
  2. Fulfilled (also known as resolved)The asynchronous operation succeeded and was converted from Pending
  3. rejectedThe asynchronous operation failed due to pending

The Promise instance represents the result of an asynchronous operation, and only the result of an asynchronous operation determines which state is currently in

No other operation can change this state. Peding can be transformed into the fulfilled and rejected, but the fulfilled and rejected cannot be transformed into each other

We can see these three states from the console. Note:

  • Promise.resolve()Returns aFulfilled stateThe Promise of the instance
  • Promise.reject()Returns aThe rejected stateYou can see the console error because Rejected is itself a Promise instanceAsynchronous error type

What good does it do to know those three states? OK, let’s look at the code below

axios({
  url: './xxx.json'}). Then (successful callback, failed callback)Copy the code

The axios ({url: }, which returns a Promise object, This asynchronous operation fails on behalf of the second parameter pending -> rejected -> THEN. This asynchronous operation fails on behalf of the second parameter pending -> fulfilled -> THEN. This asynchronous operation fails on behalf of the pending -> fulfilled -> THEN.

4.3 Chain calls for THEN and catch

Because the promise.prototype. then and promise.prototype. catch methods return a Promise object, they can be called chained

OK, let’s take a closer look at how the callback trigger mechanism works

axios({
  url: './xxx.json'}). Then (Successful callback1, failed callback1Successful callback2, failed callback2)
Copy the code

Are you a little dizzy? That’s okay, but let me explain it in detail based on the Axios example

You must have some questions:

A: Which callback function goes into the first THEN call depends on the state of the Promise returned by the first THEN call. In other words, the asynchronous operation succeeds or fails

Even if the first then calls successful callback 1, the second THEN may still enter failed callback 2, for example:

axios({
    url: './xxx.json'
})
.then((resolve_1) = > {
    return Promise.reject();
    // In this case, the state returned by then is Rejected
}, (reject_1) = > {})
.then((resolve_2) = > {
    console.log(1)},(reject_2) = > {
    // So the second then only calls its failure callback 2, which is executed here
    console.log(2)})Copy the code

2. Why don’t you say “catch”? Because catch is a syntactic sugar for then

Catch is the same thing as then having only the second argument, the failure callback

axios({
    url: './xxx.json'
})
.then((resolve_1) = > {
    return Promise.reject();
    // In this case, the state returned by then is Rejected
}, (reject_1) = > {})
.catch((reject_2) = > {
    // Execute the catch callback
    console.log(2)})// It is equivalent to
axios({
    url: './xxx.json'
})
.then((resolve_1) = > {
    return Promise.reject();
    // In this case, the state returned by then is Rejected
}, (reject_1) = > {})
.then(undefined.(reject_2) = > {
    // So the second then only calls its failure callback 2, which is executed here
    console.log(2)})Copy the code

4.4 Use Promise yourself

Above, we learned the basic use of promises by borrowing from Axios, but only on the basis that AXIos wraps promises

How can I encapsulate a promise

4.4.1 Promises without asynchronous logic

The first step

// Declare a function that returns a Promise instance
let setPromise = function () {
    return new Promise(a); }Copy the code

The second step

let isSuccess = true; // execute resolve or reject

// new Promise() accepts a function
// Specify that this function must take two arguments, which must be functions, namely, success callback, failure callback.
let setPromise = function () {
    // Resolve is a successful callback, reject is a failed callback
    let fn = (resolve, reject) = > {
        if (isSuccess) resolve('success'); // Execute resolve, then the promise state will be pending-> depressing
        else reject('failure'); Reject indicates that the promise state is pending-> REJECT
    }
    return new Promise(fn)
}
Copy the code

And you can guess, what is the state of the promiseInstance, if YOU change isSuccess to false? Try it yourself

let promiseInstance = setPromise();
console.log(promiseInstance); // ?
Copy the code

Step 3: Study the metrics

Read my notes carefully

let isSuccess = true; // execute resolve or reject

let setPromise = function () {
    let fn = (resolve, reject) = > {
        // The two strings passed here, 'success' and 'failure', become arguments in the first THEN of the Promise instance
        if (isSuccess) resolve('success');
        else reject('failure');
    }
    return new Promise(fn)
}

let promiseInstance = setPromise();

promiseInstance
.then(param= > {
    console.log(param) // 'success'
    return '2' success // The return value becomes the param in the next then
}, param= > {
    console.log(param);
    return '2' failure
})
.then(param= > {
    console.log(param) // 'success 2'
}, param= > {
    console.log(param)
})
Copy the code

Similarly, what is the output if you change isSuccess to false? Try it yourself

4.4.2 Promise with Asynchronous logic

Let’s rewrite the example in section 4.2.1 with Promise

let src = 'https://avatar-static.segmentfault.com/198/713/1987139774-59c8bdbc3b36b_huge256' // Correct path
// let src = 'xxx'; // Error path

let loadImg = new Promise((resolve, reject) = > {
    let img = new Image();

    img.onload = () = > {
        resolve(img);
    }

    img.onerror = () = > {
        reject('Image load failed');
    }

    img.src = src;
})

loadImg.then(param= > {
    console.log('Image loaded successfully');
    let body = document.querySelector('body');
    body.appendChild(param);
}, param= > {
    console.log(param);
})
Copy the code

4.5 Synthesis of multiple Promises

4.5.1 Promise. All ()

Syntax: look at the MDN documentation

Function:

The iterator elements are wrapped with promise.resolve (), and the wrapped result is synthesized, but the synthesized result is loaded according to the following principles:

  1. If there is at least one Promise, the result ispendingThe result of the composition is pending
  2. If there is at least one Promise, the result isrejectThe result of synthesis is reject [This rule has a higher priority than the previous one
  3. If all the promises turn out to befulfilledThe result of synthesis is depressing
let p1 = new Promise(() = > {});

// pending + reject === reject
Promise.all([p1, Promise.reject('error')])
.then(param= > {
    console.log(param)
}, param= > {
    console.log(param) // error
})
Copy the code

Promise.all resolves the value of the pass:

  1. ifThe result is rejected, onlyThe first state is rejectedThe result of the Promise instance is passed
  2. ifThe result is fulfilled“, will combine the results of all the fulfilled Promise instancesEncapsulate as an arrayPassing out
/ / rule one
Promise.all([Promise.reject('error1'), Promise.reject('error2')])
.then(param= > {
    console.log(param)
}, param= > {
    console.log(param) // error1
})
Copy the code
/ / rule 2
Promise.all([Promise.resolve('success1'), Promise.resolve('success2')])
.then(param= > {
    console.log(param) // [ 'success1', 'success2' ]
}, param= > {
    console.log(param)
})
Copy the code

4.5.2 Promise. Race ()

Syntax: same as promise.all ()

Function:

First of all, know that “race” means a race

Applied to the elements of an iteratorPromise.resolve()Wrap and return to the first onefulfilledorrejectedThe results of packaging

let p1 = new Promise((resolve) = > {
    setTimeout(resolve, 1000.'success');
})

let p2 = new Promise((resolve, reject) = > {
    setTimeout(reject, 500.'error');
})

// p2 first terminates, so promise.race () results in rejected, which passes a rejection to then
Promise.race([p1, p2])
.then(param= > {
    console.log(param);
}, param= > {
    console.log(param); // error
})
Copy the code

4.5.3 Implement promise.all () and promise.race ()

Promise.all()

Promise._all = (iterator) = > {
    let res = []; // THE res array is used to hold the FULFILLED Promise instances
    let cnt = 0; // this will be a big pity
    let len = iterator.length;

    return new Promise((resolve, reject) = > {
        / / for.. in.. To get the subscripts for each element of the iterator
        for (let i in iterator) {
            Promise.resolve(iterator[i])
            .then(data= > {
                cnt++;
                res[i] = data;
                if (cnt === len) resolve(res);
            }, msg= >{ reject(msg); })}})}Copy the code

Promise.race()

Promise._race = (iterator) = > {
    // See which promise instance is faster
    return new Promise((resolve, reject) = > {
        for (let i in iterator) {
            Promise.resolve(iterator[i])
            .then(data= > {
                resolve(data);
            }, msg= >{ reject(msg); })}})}Copy the code

4.6 Understand promise in life-like language

This is a big pity, which is a pity. So you can imagine that YOU go to buy oranges, and there is no purchase in the shop. I Promise to you, as long as he forgets his purchases or no longer purchases them, which is a pity. Then he will inform you

5 Async/await statement

Even though we already have promises, we’re actually still using the callback function

whileasync / awaitHelps us eliminate the callback function completely

But also note that async/await and Promise go hand in hand

5.1 async

5.1.1 Async Keyword This parameter specifies an asynchronous function

Let’s look at how async is used: adding async to a function declaration makes the function an asynchronous function

// The following fn functions are declared by async as asynchronous functions
async function fn() {};
let fn = async() = > {};Copy the code

5.1.2 Features of asynchronous Functions

1. Asynchronous functions always return a Promise instance, essentially wrapping the return value of the asynchronous function in promise.resolve ()

let fn = async() = > {// return undefined
}

console.log(fn()); // Promise {<fulfilled>: undefined}
Copy the code

Writing above produces almost the same effect as writing below

let fn = () = > {
    return Promise.resolve(undefined);
}

console.log(fn()); // Promise {<fulfilled>: undefined}
Copy the code

2. Asynchronous functions essentially provide a runtime environment for await. If they do not contain the await keyword, they perform as normal functions

What does that mean?

The async keyword simply tells the browser that this function is an asynchronous function with asynchronous code for the browser to parse.

But if you don’t write asynchronous code (as in the example above), the browser can’t do anything about it, only to parse it and discover that it’s actually a normal function.

At this point, the async keyword is really just an identifier.

5.2 await: then similar to Promise

5.2.1 Usage of await

What: The await operator is used to wait for the result of a Promise instance, essentially making promise.resolve () wrap the expression after the await operator

Limitation: Can only be used in async function

Let’s start with an example

let result;
async function fn1() {
    let p = new Promise((resolve, reject) = > {
        setTimeout(() = > {
            console.log('after 10 seconds');
            resolve(1);
        }, 10000);
    })
    result = await p;
  	console.log(2);
}

fn1();
Copy the code

As we run the code, we find some problems:

  1. If you type result over and over again in the console, the console keeps telling you:undefined, the result will be displayed only after 10 seconds
  2. console.log(2);This sentence was also executed 10 seconds later

Why is that? Since await is waiting for a result from the Promise instance p before executing result = and the following statement, await blocks subsequent code execution

5.2.2 await makes asynchronous code look like synchronous code

This title is a bit convoluted, so let’s make a change to the example in section 5.2.1 to remove await and see the result

let result;

async function fn1() {
    let p = new Promise((resolve, reject) = > {
        setTimeout(() = > {
            console.log('after 10 seconds');
            resolve(1);
        }, 10000);
    })
    result = p; / / delete await
    console.log('the result:, result); // Print result
    console.log(2);
}

fn1();
Copy the code

Console. log(‘result: ‘, result); And the console log (2); It was carried out immediately

But! Oh my god? How can the result of result be an instance of a pending Promise?

This is easy because result is an instance of Promise, which is asynchronous code, and console.log(‘result: ‘, result); Is a synchronous code, so we can only get the state before result produces the final result, which is called pending.

With await, we say result = await p; We can get the final result of p directly, and actually let the final result of P be produced, that is, after the asynchronous code is finished, we can continue to execute the following code

And isn’t that how synchronization works?

That is, await changes the order of execution of the entire code by blocking the execution of synchronous code, and it lets you write asynchronous code the same way you write synchronous code

5.3 Details of use of await

5.3.1 await a fulfilled Promise instance

There’s nothing to talk about. We can just get the results

let p = new Promise((resolve) = > {
    setTimeout(resolve, 1000.1);
})

async function fn() {
    let result = await p;
    console.log('the result:, result); / / the result: 1
}

fn();
Copy the code

5.3.2 Await a pending Promise instance

Note that if a Promise instance is always pending, await will always wait for it to transform.

That is: the code after await is never executed

let p = new Promise((resolve) = > {});

async function fn() {
    let result = /* The preceding and following code will never execute */ await p;
    console.log('the result:, result);
}

fn();
Copy the code

5.3.3 await a Promise instance of Rejected

We will find that the browser will report an error and the code behind “await” will never execute again

let p = new Promise((resolve, reject) = > {
    reject('error');
})

async function fn() {
    let result = /* The preceding and following code will never execute */ await p;
    console.log('the result:, result);
}

fn();
Copy the code

According to the documentation, we must use **try… catch… ** for error handling

let p = new Promise((resolve, reject) = > {
    reject('error');
})

async function fn() {
    try {
        let result = /* The preceding and following code will never execute */ await p;
        console.log('the result:, result);
    } catch (e) {
      	// The following sentence will be executed
        console.log(e); 
    }
}

fn();
Copy the code

Wait a minute! try… catch… You can only catch sync code errors, such as the following browser will still report an error

try {
    Promise.reject('error');
} catch (e) {
    console.log(e);
}
Copy the code

Why is it now possible to catch errors in asynchronous code with await?

“Await” makes asynchronous code look like synchronous code, so try… catch… It’s not an asynchronous code catch, it’s still a synchronous code catch

Try… catch… A catch similar to a Promise

5.4 await practice

In section 4.5 we mentioned several examples of promise.all () and promise.race (). To demonstrate the end result of both methods, we used the then method

Now we can completely rewrite with await

5.4.1 demonstrate Promise. All ()

let p1 = new Promise(() = > {});

// pending + reject === reject
Promise.all([p1, Promise.reject('error')])
.then(param= > {
    console.log(param)
}, param= > {
    console.log(param) // error
})
Copy the code

Can be rewrite into

async function fn() {
    let p1 = new Promise(() = > {});

    // pending + reject === reject
    try {
        let result = await Promise.all([p1, Promise.reject('error')])
        console.log(result);
    } catch (e) {
        console.log(e); // error
    }
}

fn();
Copy the code

I won’t show you the other examples because you can practice them yourself

5.4.2 demonstrate Promise. Race ()

let p1 = new Promise((resolve) = > {
    setTimeout(resolve, 1000.'success');
})

let p2 = new Promise((resolve, reject) = > {
    setTimeout(reject, 500.'error');
})

// p2 first terminates, so promise.race () results in rejected, which passes a rejection to then
Promise.race([p1, p2])
.then(param= > {
    console.log(param);
}, param= > {
    console.log(param); // error
})
Copy the code

Can be rewrite into

async function fn() {
    let p1 = new Promise((resolve) = > {
        setTimeout(resolve, 1000.'success');
    })

    let p2 = new Promise((resolve, reject) = > {
        setTimeout(reject, 500.'error');
    })

    // p2 terminates first, so promise.race () results in rejected
    try {
        let result = await Promise.race([p1, p2])
        console.log(result);
    } catch (e) {
        console.log(e); // error
    }
}

fn();
Copy the code