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