preface
This article covers the core content of the previous blogs and adds some new apis (fetch, Async, etc.) and new code examples. If you are interested, you can read the first few blogs
AJAX
Promise
asynchronous
Recently, when I was sorting out my previous blogs, I consulted MDN to make some records, so as to ensure that there was no wrong cognition in my previous learning knowledge.
It is against this background that I write this blog post. I always thought blogging would help me keep thinking and learning, and I did. I hope I can help others as well as myself by writing a blog to help myself learn.
Synchronous and asynchronous comparison
synchronous
To understand asynchrony, you need to know synchronization. Synchronization is executing code sequentially and seeing the results immediately. Let’s use an example to understand.
const btn = document.querySelector('button');
btn.addEventListener('click'.() = > {
alert('You clicked me! ');
let pElem = document.createElement('p');
pElem.textContent = 'This is a newly-added paragraph.';
document.body.appendChild(pElem);
});
Copy the code
If the code is too long to look at, you can run this example
The above code basically takes a button and adds a click event to it. When we click on it, an alert will be triggered, and then a P element will be generated, which says ‘This is a newly added Paragraph.’ Finally, the P element will be rendered to the page.
Not surprisingly, when we click, we run alert directly, blocking the code, and when we click OK, we continue to run the code below, generating the P element.
This example illustrates the drawback of synchronized code: when individual code blocks, subsequent code is affected.
Blocking can occur for a variety of reasons, including the need to complete a very time-consuming task, such as running a million operations, or making a network request.
This can be a very frustrating experience, because with today’s multi-core capabilities, we should be asking computers to do more tasks at once, not one at a time. JS is single-threaded, and the design may not be modified in the future, so to complete multiple time-consuming tasks without blocking under a single thread, it is necessary to increase the thread-assisted ability of JS, and JS designers use web workers of auxiliary threads to help complete time-consuming tasks. But essentially, JS remains single-threaded because other threads cannot modify the DOM.
So although multithreading is added to help with computation, it still only reduces the problem of code blocking. Although Web workers are useful, they do not have the permission to update the DOM, which is the right of the main thread.
Imagine that we need web workers to do a task to get an image, and I need this image to update the DOM on the main thread. In this case, an error is likely to be reported because it is possible that the Web workers did not get the image and the main thread’s DOM update operation will already be running.
To solve this problem, the browser runs something asynchronous.
asynchronous
Now that we understand what synchronous is, can we modify the above code to make it asynchronous
const btn = document.querySelector('button');
btn.addEventListener('click'.() = > {
setTimeout(() = >{alert('You clicked me! '); },2000)
// alert('You clicked me! ') deletes this line
let pElem = document.createElement('p');
pElem.textContent = 'This is a newly-added paragraph.';
document.body.appendChild(pElem);
});
Copy the code
In this case, you’ll notice that you render first and then alert, because setTimeout is browser-approved asynchronous.
Callbacks and promise
There are two schools of asynchrony, callbacks and the use of promises.
callbacks
Callbacks use callback functions to manually order code execution.
The second argument in the btn.addeventListener (‘click’, () => {}) example above is the callback function, which means that the code will be executed after clicking the event.
When we pass a callback function as an argument to another function, we simply pass the callback definition as an argument — the callback is not executed immediately, the callback is executed asynchronously somewhere in the containing function, and the containing function is responsible for executing the callback when appropriate.
function fn(callback){
callback()
}
function fn1(){}
fn(fn1)
Copy the code
The code above is a simple callback.
We can write the simplest AJAX example ourselves
function ajax(url,method,callback){
const request=new XMLHttpRequest()
request.open(method,url)
request.onReadyStatechange=() = >{
if(request.status===200 && request.readyState===4){
callback(request.response)
}
}
request.send()
}
function fn(X){
console.log(X)
}
ajax('5.json'.'GET',fn)
Copy the code
The above code first defines an Ajax function, then a FN function. When I run Ajax and pass the parameters, I will perform ajax operations inside ajax, and finally callback fn to print request.response
Note that not all callbacks are asynchronous; array.prototype. forEach is a synchronous callback
Promise
Promise is a new school of asynchronous code that has three main states
When a promise is created, it is neither a success nor a failure state. This state is called pending.
When a promise returns, it’s called Resolved.
A successful resolved promise is called fullfilled. It returns a value that can be accessed by linking the.then() block to the end of the Promise chain. The executor function in the.then() block will contain the return value of the Promise.
An resolved promise that cannot succeed is called rejected. It returns a reason, an error message, explaining why the promise was rejected. You can access this reason by linking the.catch() block to the end of the promise chain.
Promises produce only two state transitions:
- Never finished to succeed
- Never complete to fail
let promise = new Promise((resolve,reject) = >{
if('success'){
resolve('Success! ')}else{
reject(reason)
}
})
Copy the code
The Promise constructor takes one function as an argument, which in turn takes two functions as arguments, and executes resolve if the asynchronous operation succeeds, reject if it fails.
promise.then((message) = >{console.log(message)},(error) = >{console.error(error)})
Copy the code
Then receives two callbacks, the first for a successful promise and the second for a failed promise.
It could also be written like this
promise.then((message) = >{console.log(message)}).catch((error) = >{console.error(error)})
Copy the code
The parameter in then is optional. Catch (failureCallback) is the short form of THEN (NULL)
Macro task, micro task
SetTimeout is typical for macro tasks, Promise is typical for microtasks.
In the following code, the promise is always executed first, followed by setTimeout
setTimeout(() = >{console.log('Macro task after execution')})
Promise.resolve('Microtask first').then((r) = >{console.log(r)})
"Microtask first"
"Macro task after execution"
Copy the code
Chain calls
It is a common requirement to perform two or more asynchronous operations in a row, and the.then method returns a promise object, so it can be called chained.
promise.then(() = >{})
.then(() = >{})
.catch(() = >{console.error()})
Copy the code
Normally, when an exception is thrown, the browser follows the Promise chain looking for the next onRejected failed callback or the one specified by.catch().
That is, no matter how many previous “then” s there are, if there is a problem in one step, the.catch method will be executed
promise.all
If you want to run some code after a bunch of Promises are done, then obviously isn’t going to work.
You can do this using the cleverly named promise.all () static method. This takes an array of Promises as an input argument and returns a new Promise object that will only be satisfied if all promises in the array are satisfied. It looks something like this:
Promise.all([a, b, c]).then(values= >{... });Copy the code
If they are both implemented, the result in the array is passed as an argument to the executor function in the.then() block. If any of the promises passed to promise.all () reject, the entire block will reject.
If the parameters contain non-promise values, they are ignored, but are still placed in the return array
Promise.all([1.2]).then((s) = >{console.log(s)})
Copy the code
promise.allsettled
The Promise. All above can only return all results after all success, if a failure to stop and return a failed response, obviously not in accordance with our wishes, we must always collect all results, which is the result of this API
const p=[Promise.reject(0),Promise.resolve(1)]
Promise.allSettled(p)
.then((response) = >{console.log(response)})
/* [{ reason: 0, status: "rejected" },{ status: "fulfilled", value: 1 }] */
Copy the code
There are compatibility issues with this API, and many times older browsers don’t work, so we need some way to emulate it.
The simulation method is to use Promise. All does not return failure
const p=[Promise.reject(0)
.then((r) = >{return {'ok':r}},(r) = >{return {'no ok':r}}),
Promise.resolve(1)
.then((r) = >{return {'ok':r}},(r) = >{return {'no ok':r}})]
Promise.all(p).then((r) = >{console.log(r)})
/* [{ no ok: 0 },{ ok: 1 }] */
Copy the code
The above method can be encapsulated and optimized, which is not covered here
promise.finally
After the promise completes, you might want to run the last piece of code, whether it’s fullfilled or rejected. Previously, you had to include the same code in the.then() and.catch() callbacks, for example
myPromise
.then(response= > {
doSomething(response);
runFinalCode();/ / repeat
})
.catch(e= > {
returnError(e);
runFinalCode(); / / repeat
});
Copy the code
In modern browsers, the.finally() method is available, which links to the end of the regular promise chain, allowing you to reduce code duplication and perform operations more elegantly. The above code can now be written as follows
myPromise
.then(response= > {
doSomething(response);
})
.catch(e= > {
returnError(e);
})
.finally(() = > {
runFinalCode();
});
Copy the code
Async and await: async syntactic sugar
In simple terms, they are based on Promises syntectic candy that makes asynchronous code easier to write and read. Using them, asynchronous code looks more like old-fashioned synchronous code, so they’re well worth learning.
Async keyword
async function hello() { return "Hello" };
hello();
//Promise {<fulfilled>: "Hello"}
Copy the code
Without further explanation, after the async keyword is added to the above code, this function returns a promise. This is the foundation of asynchrony, so to speak, and asynchronous JS today uses Promises.
The arrow function
let hello=async() = > {return 'hello'}
Copy the code
Now we can use the dot then method
hello().then((mes) = >{console.log(mes)})
Copy the code
The await keyword
Await only works in asynchronous functions. It actively suspends code on the line until the promise is complete and then returns the result value. Await should be followed by a promise object
We can make an actual example
const p=new Promise((resolve,reject) = >{
setTimeout(resolve,3000.'doing')})const r=new Promise((resolve,reject) = >{
setTimeout(resolve,0,p)
})
const o=r.then((mes) = >{
return mes+'=>done'
})
o.then((mes) = >{console.log(mes)}).catch((error) = >{console.log(error)})
//doing => done
Copy the code
In the code above, r will get the result of P, and then chain call.
We can encapsulate with async+await
function promise(ms, mes) {
return new Promise((resolve, reject) = > {
setTimeout(resolve, ms, mes);
});
}
async function fn() {
const p = await promise(3000."doing");
console.log(p); // doing
const r = await promise(0, p);
console.log(r); //doing
const o = await (r + "=>done");
console.log(o); //doing =>done
}
fn();
Copy the code
You can see that the asynchronous code above is written as synchronous code.
Async and await must be used together to have an asynchronous effect. Using async alone is still synchronous code and only returns a promise object
Error handling
Error handling is the key when using the async/await keyword, which is typically written to catch errors
function ajax(){
return Promise.reject(1)}async function fn(){
try{
const result=await ajax()
console.log(result)
}catch(error){
console.log(error)
}
}
fn()
Copy the code
Now we can do better
function ajax(){
return Promise.reject(1)}function ErrorHandler(error){
throw Error(error)
}
async function fn(){
const result=await ajax().then(null.(error) = >{ErrorHandler(error)})
console.log('result',result)
}
fn()
Copy the code
You can throw an Error with throw Error instead of using an ErrorHandler to return the result. The subsequent code will not execute
Contagion of await
function async2(){
console.log('async2')}async function fn(){
console.log('fn')
await async2() / / synchronization
console.log('Am I asynchronous? ')
}
fn()
console.log('end')
//fn
//async2
//end
// I am asynchronous?
Copy the code
The final console.log(‘ What step am I? ‘) is after the ‘await’ keyword, which means it is asynchronous. If we want to execute synchronous code, we should put ‘await’ above it, because sometimes’ await ‘gives us confusion and makes us think that code without’ await ‘keyword is synchronous.
You may be wondering if the first line of log is also asynchronous, but this code will tell you that just because async is written doesn’t mean it’s an asynchronous function.
let a=0
async function fn(){
console.log(a)
await Promise.resolve(333).then((r) = >{console.log(r)})
console.log('What step am I? ')
}
fn()
console.log(++a)
/ / the result
/* 0 1 333 "What step am I?" * /
Copy the code
Serial and parallel
“Await” is inherently serial, and “serial” means to execute in order.
function async2(delay){
return new Promise((resolve) = >{
setTimeout(() = >{
console.log('execution')
resolve()
},delay)
})
}
async function fn(){
await async2(5000)
await async2(2000)
await async2(1000)
}
fn()
Copy the code
Since async and setTimeout have no effect at the same time, SO I use the above code to experiment, the log station will print respectively after 5 seconds, which means that the default is to execute await in order
If you want parallelism, you can use the promise. all or forEach methods
function fn(){
await Promise.all([async2(5000),async2(2000),async2(1000)])}Copy the code
function async2(delay){
return new Promise((resolve) = >{
setTimeout(() = >{
console.log('execution')
resolve()
},delay)
})
}
function fn3(ms){
return function fn(){
async2(ms)
}
}
[fn3(5000),fn3(2000),fn3(1000)].forEach(async (v)=>{
await v()
})
Copy the code
Combined with FETCH
Fetch is XMLHttpRequest using the Promise version
fetch('products.json').then(function(response) {
return response.json();
}).then(function(json) {
console.log(json)
}).catch(function(err) {
console.log('Fetch problem: ' + err.message);
});
Copy the code
The code above means that fetch applies for a JSON data, and then jsonizes the data and prints it.
Convert to async and await methods
const promise=() = >{
try{
const j=await fetch('products.json')
const result=await j.json()
console.log(result)
}catch(error){
console.log(error)
}
}
promise()
Copy the code
conclusion
Asynchronous JS, most of the use of Ajax to generate web work, understanding Promise is the basis of our learning, modern JS is based on Promise to work.
Now that we know about promises, we can implement promises more easily with async and await syntactic sugar, avoiding a lot of then nesting.
Reference documentation
Asynchronous JavaScript
Using the fetch
Promise
XMLHttpRequest