The execution environment of the Javascript language is single-threaded. You can only complete one task at a time. If there are multiple tasks, queue up to execute them one by one. The first task is completed, and then the second task is executed.

This implementation mode is simple and the execution environment is relatively simple. However, with the increasing complexity of front-end services and the increasing number of transactions and requests, this single-threaded execution mode is bound to be inefficient in complex services. As long as one task takes a long time, subsequent tasks must be queued, which will delay the execution of the entire program. A common browser non-response (suspended animation) is usually the result of a single piece of Javascript code running for so long (such as an infinite loop) that the entire page gets stuck in one place and no other task can be performed.

In order to avoid and solve this problem, JS language divides the task execution mode into asynchronous and synchronous. “Synchronous mode” is the mode of the previous section, the latter task wait for the end of the previous task, and then execute, the execution sequence of the program is consistent with the order of the task, synchronous; Asynchronous mode “is completely different, each task has one or more of the callback function (the callback), before the end of a task, not to perform a task, after but the callback function, after a task is before the end of a task before execution, so the program execution order the order is not consistent with the task, asynchronous.

“Asynchronous mode” is very important. On the browser side, long operations should be performed asynchronously to prevent the browser from becoming unresponsive. The best example of this is Ajax operations. On the server side, “asynchronous mode” is even the only mode, because the execution environment is single-threaded, and if you allow all HTTP requests to be executed synchronously, the server performance deteriorates dramatically and quickly becomes unresponsive.

      1. Callback function 

The most basic method of asynchronous programming.

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.

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.

Here’s a common life analogy: You drop your girlfriend off at the end of your date. As you’re leaving, you’ll say, “Text me when you get home. I was worried about you.” And then your girlfriend actually texted you when she got home. This is actually a callback process. You leave a parameter function (asking your girlfriend to send you a message) to your girlfriend, and then your girlfriend goes home, and the return action is the main function. She has to get home, the main function is done, and then the function that’s passed in, and then you get a message.

Suppose you have two functions f1 and f2, the latter waiting for the result of the former.

     


f1();
f2(); Copy the code


If f1 is a time-consuming task, consider rewriting F1 to write f2 as a callback function of F1.



Function f1(callback){setTimeout(function () {// f1(callback); }, 1000); }Copy the code






f1(f2);Copy the code


In this way, we change the synchronous operation to asynchronous operation. F1 does not block the program execution, which is equivalent to executing the main logic of the program first and postponing the execution of time-consuming operation.

Another example:


Function A(callback) {callback(); Console. log(' I am the main function '); } function B(){setTimeout("console.log(' I am a callback ')", 3000); } // call the main function, pass function B into A(B); // I am the main function. I am the callback functionCopy the code


In the above code, we define the main function and the callback function, then call the main function and pass in the callback function.

When we define the main function, we let the code execute the callback() callback first, but the output is the content of the later output callback. This means that the main function doesn’t have to wait for the callback function to finish executing and can execute its own code. So callbacks are usually used for time-consuming operations. Such as Ajax requests, such as processing files, etc.

Here’s a more mundane example:


Q: If you have something to do, you go to the next dormitory to look for your classmate. </strong>< /strong>< /strong>< /strong>< /strong>< /strong>< /strong>< /strong>< /strong>< /strong>< /strong>< /strong>< /strong>< /strong>< /strong>< /strong>< /strong>< /strong>< /strong> Can I just wait in the next dorm until my classmates come back? Yes, it's just that you'll have to wait while you can do other things. The original non-blocking asynchronous call becomes a blocking synchronous call. JavaScript callbacks are used in asynchronous invocation scenarios and perform better with callbacks than polling.Copy the code


Callbacks are usually executed last in synchronous situations, and may not be executed in asynchronous situations because the event was not triggered or the condition was not met, so please ignore the glitch in the previous example. It is not necessary for the callback to be executed.

The advantages and disadvantages of the callback function are also added:

  • Resource loading: callback after loading JS file dynamically, callback after loading iframe, callback after Ajax operation, callback after loading image, Ajax etc.
  • DOM events and Node.js events are based on callback mechanisms (Node.js callbacks can have multiple layers of nested callback problems).
  • SetTimeout has a delay of 0. This hack is often used because the function called by setTimeout is essentially a callback.
  • Chain call: Chain call, in the assignment editor (setter) method (or itself has no return value method) is easy to implement chain calls, and device (getter) values are relatively bad implementation chain calls, because you need value is return a pointer to the data you need rather than this, if you want to achieve chain method, can be done using the callback function.
  • Calls to setTimeout and setInterval get their return values. Since both functions are asynchronous, that is: Their call sequence and the main flow of the program are relatively independent, so there is no way to wait for their return values in the main body. When they are opened, the program will not stop and wait, otherwise the significance of setTimeout and setInterval will be lost, so it is meaningless to use return. Only callback can be used. The purpose of the callback is to inform the proxy function of the results of the timer execution for timely processing.

The advantage of this approach is that it is relatively easy to understand, multiple events can be bound, each event can specify multiple callback functions, and it can be “decouple”, which is good for modularity. The disadvantage is that the entire program has to be event-driven, and the flow becomes very unclear.

  2. Promise

With the release of the ES6 standard, the solution for handling asynchronous data flows has taken a new turn. Promise was one of those things. We all know that in traditional Ajax requests, when there is a dependency on data between asynchronous requests, it can lead to ugly layers of callbacks, which can easily confuse the code logic and make it difficult to read and maintain, commonly known as “callback hell.” On the other hand, error-handling code is often coupled with normal business code, resulting in extremely ugly code. To make programming better, we needed to introduce Promises to reduce the complexity of asynchronous programming.

So in a way, Promise is an advanced way to handle asynchronous programming with the callbacks mentioned above. First, Promise is a specification from CommandJS that aims to provide a unified interface for asynchronous programming.

In a nutshell, the idea of promises is that each asynchronous task returns a Promise object that has a THEN method that allows callback functions to be specified. Take the form of:


f1().then(f2);Copy the code





function f1(){var dfd = $.Deferred(); SetTimeout (function () {// f1 ddfd.resolve (); }, 500); return dfd.promise; }Copy the code


The advantage of this is that the callbacks become chained, the flow of the program can be clearly seen, and there is a whole set of supporting methods that can achieve many powerful functions. This is one of the convenient ways that Promise handles asynchronous programming.

Here is another example of specifying multiple callback functions of the form:


f1().then(f2).then(f3);Copy the code








f1().then(f2).fail(f3);Copy the code


As a side note, in Promise, if you add a callback after a task has already been completed, the callback will execute immediately. So you don’t have to worry about missing an event or signal. The downside of this approach is that it is relatively difficult to write and understand.

To expand on promises: Promises are essentially a special Javascript object that reflects the “end value of an asynchronous operation.” “Promise” literally means to expect, so it also represents some kind of Promise that the object will eventually return a value to you regardless of whether your asynchronous operation succeeds or not.

Code sample


const promise = new Promise((resolve, reject) => { $.ajax('https://github.com/users', (value) => { resolve(value); }).fail((err) => { reject(err); }); }); promise.then((value) => { console.log(value); },(err) => { console.log(err); }); Promise.then (value => console.log(value)). Catch (err => console.log(err));Copy the code


In the example above, the resolve callback is called to process the result after the Ajax request succeeds, and the Reject callback is called to process the error if the request fails. The Promise object contains three states, which are pending, depressing and Rejected. These three states are analogous to pending,success, and error in ajax data request processes. After the initial request is sent, the state is Pending, which means that the process is being completed. This state is intermediate and one-way and irreversible. After the successful value acquisition, the state will be fulfilled. Then the successfully obtained value will be stored, which can be further processed by calling the callback function passed in by the THEN method. If it fails, the state changes to Rejected, and the error can be thrown or rejected.

The basic syntax for promises is as follows

  • A Promise instance must implement the then method

  • Then () must accept two functions as arguments

  • Then () must return an instance of Promise


Eg < script SRC = "https://cdn.bootcss.com/bluebird/3.5.1/bluebird.min.js" > < / script > / / if low version browser does not support the Promise, <script type="text/javascript"> function loadImg(SRC) {var promise = new promise (resolve, reject) { var img = document.createElement('img') img.onload = function () { resolve(img) } img.onerror = function () { Reject (' image loading failure ')}. Img SRC = SRC}) return promise} var SRC = 'https://www.imooc.com/static/img/index/logo_new.png' var  result = loadImg(src) result.then(function (img) { console.log(1, img.width) return img }, function () { console.log('error 1') }).then(function (img) { console.log(2, img.height) }) </script>Copy the code


Promise can do more than that, for example, if there are several asynchronous tasks that need to be done first for task 1, then for task 2 if they succeed, and then for any task that fails, no longer continue and an error handler is executed. To perform such asynchronous tasks serially, you need to write layer upon layer of nested code without promises.

With promises, we simply write job1.then(job2).then(job3).catch(handleError); Job1, joB2, and job3 are Promise objects.

For example, if we want to load the first image and then load the second image, if one of them fails, we will execute the error function:





Var src1 = 'https://www.imooc.com/static/img/index/logo_new.png' var result1 = loadImg (src1) / / var result1 is Promise object Src2 = 'https://img1.mukewang.com/545862fe00017c2602200220-100-100.jpg' var result2 = loadImg (src2) / / result2 object is a Promise Result1. then(function (img1) {console.log(' the first image is loaded ', Img1.width) return result2 // chain}). Then (function (img2) {console.log(' the second image is loaded ', img2.width) }).catch(function (ex) { console.log(ex) })Copy the code



The THEN method can be called more than once by the same PROMISE, and must return a Promise object

A common approach to Promise

In addition to executing several asynchronous tasks sequentially, promises can also execute asynchronous tasks in parallel

Imagine a page chat system where we need to obtain the user’s personal information and friend list from two different urls. These two tasks can be performed in parallel, using promise.all () as follows:


var p1 = new Promise(function (resolve, reject) { setTimeout(resolve, 500, 'P1'); }); var p2 = new Promise(function (resolve, reject) { setTimeout(resolve, 600, 'P2'); }); Then: promise.all ([p1, p2]). Then (function (results) {console.log(results); // Get an Array: ['P1', 'P2']});Copy the code






var p1 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 600, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
    console.log(result); // 'P1'
});
Copy the code


Since P1 executes faster, the Promise’s then() will get the result ‘p1’. P2 continues execution, but the results are discarded.

All accepts an array of Promise objects. After all completion, success will be executed uniformly.

Promise.race accepts an array of Promise objects, and as long as one completes, success is executed.

Modify the above example to further understand both:


var src1 = 'https://www.imooc.com/static/img/index/logo_new.png'
     var result1 = loadImg(src1)
     var src2 = 'https://img1.mukewang.com/545862fe00017c2602200220-100-100.jpg'
     var result2 = loadImg(src2)
     Promise.all([result1, result2]).then(function (datas) {
         console.log('all', datas[0])//<img src="https://www.imooc.com/static/img/index/logo_new.png">
         console.log('all', datas[1])//<img src="https://img1.mukewang.com/545862fe00017c2602200220-100-100.jpg">
     })
     Promise.race([result1, result2]).then(function (data) {
         console.log('race', data)//<img src="https://img1.mukewang.com/545862fe00017c2602200220-100-100.jpg">
     })
Copy the code


If we combine promises, we can combine many asynchronous tasks in parallel and serial fashion.

Promise.reject(reason) : Returns a new Promise object, using the reason value to change the state directly to Rejected.


const promise2 = new Promise((resolve, reject) => {
  reject('Failed');
});

const promise2 = Promise.reject('Failed');Copy the code


The two notations above are equivalent.

Promise.resolve(value): Returns a new Promise object that is resolved. Similar to reject, the following two are equivalent.


const promise2 = new Promise((resolve, reject) => {
  resolve('Success');
});

const promise2 = Promise.resolve('Success');Copy the code


Then uses this method to access the value or cause of the error. Its callback function is used to handle the return value of asynchronous processing.

Catch uses this method to catch errors and process them.

3. Introduction and usage of Async/Await

Introduction to the

  • Async /await is a new way to write asynchronous code. Previous methods have callback functions and promises.
  • Async /await is implemented based on promises and cannot be used for normal callback functions.
  • Async /await, like Promise, is non-blocking.
  • Async /await makes asynchronous code look like synchronous code, which is part of its magic.

grammar

Use the Promise example and the ASyn /await example to demonstrate two pieces of code:

promise


const makeRequest = () =>
  getJSON()
    .then(data => {
      console.log(data)
      return "done"
    })

makeRequest()Copy the code






const makeRequest = async () => {
  console.log(await getJSON())
  return "done"
}

makeRequest()Copy the code


They are slightly different:

  • The function is preceded by the ayNC keyword. The await keyword can only be used within functions defined by AYNC. The async function implicitly returns a promise whose reosolve value is the value of the function return. (The reosolve value in the example is the string “done”)

  • Point 1 implies that we cannot use await in the outermost code because it is not inside the async function.


// Cannot use await await await makeRequest() in outermost code // this is makeRequest(). Then ((result) => {// code})Copy the code


Await getJSON() means console.log will wait until getJSON’s promise succeeds in reosolve.

What are the advantages of async/await over Promise

1. The concise

As you can see from the example, using Async/Await is a significant code savings. We don’t need to write.then, we don’t need to write anonymous functions to handle the resolve value of a Promise, we don’t need to define redundant data variables, and we avoid nested code. These small advantages quickly add up and will become more apparent in later code examples.

2. Error handling

Async/Await lets try/catch handle both synchronous and asynchronous errors. In the following Promise example, try/catch cannot handle json.parse because it is in the Promise. We need to use.catch, which makes our error handling code redundant. Also, our actual production code will be more complex.





Const makeRequest = () => {try {getJSON(). Then (result => {// json.parse) Console. log(data)}) Error handling asynchronous code //. Catch ((err) => {// console.log(err) //})} Catch (err) {console.log(err)}}Copy the code








const makeRequest = async () => {
  try {
    // this parse may fail
    const data = JSON.parse(await getJSON())
    console.log(data)
  } catch (err) {
    console.log(err)
  }
}
Copy the code


3. Conditional statements

In the following example, you need to retrieve the data and then, based on the returned data, decide whether to return directly or continue to retrieve more data.

const makeRequest = () => {
  return getJSON()
    .then(data => {
      if (data.needsAnotherRequest) {
        return makeAnotherRequest(data)
          .then(moreData => {
            console.log(moreData)
            return moreData
          })
      } else {
        console.log(data)
        return data
      }
    })
}
Copy the code

The code is a pain to look at. Nesting (6 layers), parentheses, and return statements can get confusing, and they just need to deliver the final result to the outermost Promise.

The above code is written with async/await to greatly improve readability:


const makeRequest = async () => {
  const data = await getJSON()
  if (data.needsAnotherRequest) {
    const moreData = await makeAnotherRequest(data);
    console.log(moreData)
    return moreData
  } else {
    console.log(data)
    return data    
  }
}
Copy the code


4. The median

You’ve probably encountered a scenario where you call promisE1, use the results returned by PromisE1 to call PromisE2, and then use the results of both to call promise3. Your code might look something like this:


const makeRequest = () => {
  return promise1()
    .then(value1 => {
      return promise2(value1)
        .then(value2 => {        
          return promise3(value1, value2)
        })
    })
}
Copy the code








const makeRequest = () => {
  return promise1()
    .then(value1 => {
      return Promise.all([value1, promise2(value1)])
    })
    .then(([value1, value2]) => {      
      return promise3(value1, value2)
    })
}Copy the code


This approach sacrifices semantics for readability. There is no reason to put Value1 and Value2 in an array other than to avoid nesting.

With async/await, the code becomes surprisingly simple and intuitive.


const makeRequest = async () => {
  const value1 = await promise1()
  const value2 = await promise2(value1)
  return promise3(value1, value2)
}Copy the code


5. The error stack

The following example calls multiple promises, assuming an error is thrown somewhere in the Promise chain:


const makeRequest = () => {
  return callAPromise()
    .then(() => callAPromise())
    .then(() => callAPromise())
    .then(() => callAPromise())
    .then(() => callAPromise())
    .then(() => {
      throw new Error("oops");
    })
}

makeRequest()
  .catch(err => {
    console.log(err);
    // output
    // Error: oops at callAPromise.then.then.then.then.then (index.js:8:13)
  })
Copy the code


The error stack returned in the Promise chain gives no clue as to where the error occurred. Worse, it can mislead us; The only function in the error stack is called callAPromise, but it has nothing to do with the error. (File names and line numbers are useful).

However, the error stack in async/await points to the function where the error is:


const makeRequest = async () => {
  await callAPromise()
  await callAPromise()
  await callAPromise()
  await callAPromise()
  await callAPromise()
  throw new Error("oops");
}

makeRequest()
  .catch(err => {
    console.log(err);
    // output
    // Error: oops at makeRequest (index.js:7:9)
  })
Copy the code


In a development environment, this is not a huge advantage. However, it can be very useful when you analyze error logs in production environments. At this point, it is better to know that the error occurred in the makeRequest than in the THEN chain.

6. Debugging

Last but not least, async/await makes debugging code much easier. Debugging promises can be painful for two reasons:

  • You cannot set breakpoints in arrow functions that return expressions


const markRequest = () => {
    return callAPromise ()
        .then (() => callAPromise())
        .then (() => callAPromise())
        .then (() => callAPromise())
        .then (() => callAPromise())

}
Copy the code





const markRequest = async () => {
    await callAPromise()
    await callAPromise()
    await callAPromise()
    await callAPromise()
    await callAPromise()
}
Copy the code


conclusion

As for different asynchronous programming processing schemes commonly used, my personal opinion is that appropriate and efficient schemes can be selected according to different business scenarios. Each has its own advantages and disadvantages, so there is no need to step on one at the top. Although the technology continues to develop and optimize, some technologies will not be eliminated so fast, and it is reasonable to exist.






binbinsilk

This article source: nuggets for reprint please contact the original author