1. An interview question

Some time ago interview, more investigation is JS asynchronous programming related knowledge points, now, it is their turn to share technology, so I want to js asynchronous programming learning, do a summary. The following demo summarizes most interview questions:

for(var i = 0; i < 3; i++) {
   setTimeout(function() {
       console.log('timeout' + i);
   })
}

new Promise(function(resolve) {
    console.log('promise1');
    for(var i = 0; i < 1000; i++) {
        i == 99 && resolve();
    }
    console.log('promise2');
}).then(function() {
    console.log('then1');
})

console.log('global1');Copy the code

Through verification, the result of the demo is as follows:

clipboard.png

But why is such a result, we may need to understand the following two points of knowledge first

Two, two prerequisite knowledge points

2.1 Multithreading in the browser kernel

clipboard.png

The browser kernel is multi-threaded, and they work with each other under the control of the kernel to keep in sync. A browser implements at least three resident threads: javascript engine threads, GUI rendering threads, and browser event-triggering threads.

1) JS engine, based on event-driven single thread execution, JS engine has been waiting for the arrival of tasks in the task queue, and then to process, browser at any time there is only one JS thread running JS program. 2) THE GUI thread, which executes when the interface needs to be redrawn or when some operation causes backflow. It is mutually exclusive with the JS engine. 3) The browser event trigger thread. When an event is triggered, the thread will add the event to the end of the queue to be processed by the JS engine. These events can come from the code block currently executed by the JavaScript engine, such as setTimeOut, or from other threads in the browser kernel, such as mouse click. AJAX asynchronous requests, etc., but due to the single-threaded RELATIONSHIP of JS, all these events have to queue up for the JS engine to process.

2.2 Event loop Mechanism

clipboard.png

1) Task queues are divided into macro-tasks and micro-tasks. In the latest standard, they are called tasks and Jobs respectively.

2) Macro-task includes: Script, setTimeout, setInterval, setImmediate, I/O, UI rendering.

NextTick, Promise, Object. Observe (deprecated), MutationObserver(new html5 feature)

SetTimeout /Promise etc we call task sources. What enters the task queue is the specific execution task they specify.

The order of the event loop determines the order in which the JavaScript code is executed. It starts the first loop with script(the whole code). The global context then enters the function call stack. Until the call stack is empty (only global), then all micro-tasks are executed. When all executable micro-tasks have been executed. The loop starts again with macro-Tasks, finds one task queue to complete, and then executes all macro-tasks, and so on.

From this sequence of events, we can see why the interview title mentioned above is such an output. Let’s look at three types of asynchronous programming implementations.

Three, three types of asynchronous programming

3.1 Callback functions

demo1:

// A simple packagefunction want() {
    console.log('Here's the code you want to execute.');
}

function fn(want) {
    console.log('This is a bunch of different code being executed.'); // Execute the callback want && want(); } fn(want);Copy the code

demo2:

//callback hell

doSomethingAsync1(function() {doSomethingAsync2(function() {doSomethingAsync3(function() {doSomethingAsync4(function() {doSomethingAsync5(function(){
                    // code...
                });
            });
        });
    });
});Copy the code

A problem, can be found in the callback function nested layers under the condition of not deep, the code is easy to understand and maintain, once the nested layers deepen, would be a “correction” pyramid, like demo2, if the inside each callback function and contains a lot of business logic, the whole block of code will become very complicated. From a logical correctness point of view, there is nothing wrong with writing these types of callback functions, but as business logic grows and becomes more complex, its shortcomings quickly become apparent and it becomes too painful to maintain. This is called callback hell.

Another problem with callbacks is that we can’t catch an exception in a callback outside of the callback function, so we use a try catch to catch an exception, so we try to catch an exception in a callback

clipboard.png


3.2 Event Listening (Event Publish/Subscribe)

Event listening is a very common asynchronous programming pattern. It is a typical logic separation method, which is very useful for code decoupling. In general, we need to consider which parts are immutable and which are easy to change, encapsulate the immutable parts inside the component for external invocation, and expose the parts that need customization for external processing. In a sense, the design of events is the interface design of components. 1) jQuery event listener

    $('#btn').on('myEvent'.function(e) {
        console.log('There is my Event');
    });
    $('#btn').trigger('myEvent');Copy the code

2) Publish/subscribe

    var PubSub = function(){
        this.handlers = {}; 
    };
    PubSub.prototype.subscribe = function(eventType, handler) {
        if(! (eventTypeinthis.handlers)) { this.handlers[eventType] = []; } this.handlers[eventType].push(handler); // Add event listenersreturnthis; // Return the context for the chain call}; PubSub.prototype.publish =function(eventType) {
        var _args = Array.prototype.slice.call(arguments, 1);
        for(var i = 0, _handlers = this.handlers[eventType]; i < _handlers.length; i++) { _handlers[i].apply(this, _args); // traversal event listener}returnthis; }; var event = new PubSub; // Construct PubSub instance event.subscribe('list'.function(msg) {
        console.log(msg);
    });
    event.publish('list', {data: ['one,'.'two']});
    //Object {data: Array[2]}Copy the code

Asynchronous programming in this pattern is essentially implemented through callback functions, so the problems of nested callback and failure to catch exceptions mentioned in 3.1 still exist. Let’s see if ES6 provides Promise objects to solve these two problems.

3.3 Promise object

ES 6 natively provides a Promise object, which represents an event whose outcome will be known in the future (typically an asynchronous operation) and provides a unified API for further processing. The Promise object allows asynchronous operations to be expressed in the same way as synchronous operations, avoiding nested layers of asynchronous callbacks, making the code clearer, easier to maintain, and catching exceptions.

A simple example:

function fn(num) {
  return new Promise(function(resolve, reject) {
    if (typeof num == 'number') {
      resolve();
    } else {
      reject();
    }
  })
  .then(function() {
    console.log('The argument is a number value');
  })
  .then(null, function() {
    console.log('Argument is not a number value');
  })
}
fn('haha');
fn(1234);Copy the code

Why Promise can be so asynchronous programming, in this we simply analyze the Promise implementation process: 1) minimalist Promise prototype

// A minimalist promisefunctionPromise(fn) { var value = null, callbacks = []; // Callbacks are arrays because there may be many callbacks this.then = at the same timefunction (onFulfilled) {
    callbacks.push(onFulfilled);
  };

  function resolve(value) {
    callbacks.forEach(function (callback) {
      callback(value);
    });
  }

  fn(resolve);
}Copy the code
  • If the function inside a Promise is a synchronous function, we add some processing to ensure that all callbacks are registered for the THEN method before resolve executes.
  • By using the setTimeout mechanism, the logic that performs the callback in Resolve is placed at the end of the JS task queue to ensure that the callback function of the then method has been registered by the time resolve executes.

2) Add delay processing

// Minimal promise prototype, add delay processingfunctionPromise(fn) { var value = null, callbacks = []; // Callbacks are arrays because there may be many callbacks this.then = at the same timefunction (onFulfilled) {
    callbacks.push(onFulfilled);
  };

  function resolve(value) {
    setTimeout(function() {
        callbacks.forEach(function (callback) {
            callback(value);
        });
    }, 0)
  }

  fn(resolve);
}Copy the code
  • If the Promise asynchronous operation is successful, then callbacks registered before the asynchronous operation will be executed, but callbacks registered after the Promise asynchronous operation will never be executed again, which is obviously not what we want

3) Add state judgment

// Simple promise prototype, add state judgmentfunction Promise(fn) {
  var state = 'pending',
      value = null,
      callbacks = [];

  this.then = function (onFulfilled) {
      if (state === 'pending') {
          callbacks.push(onFulfilled);
          return this;
      }
      onFulfilled(value);
      return this;
  };

  function resolve(newValue) {
      value = newValue;
      state = 'fulfilled';
      setTimeout(function () {
          callbacks.forEach(function (callback) {
              callback(value);
          });
      }, 0);
  }

  fn(resolve);
}Copy the code

4) Chain promise

// Minimalist promise prototype, chain promisefunction Promise(fn) {
  var state = 'pending',
      value = null,
      callbacks = [];

  this.then = function (onFulfilled) {
      return new Promise(function (resolve) {
          handle({
              onFulfilled: onFulfilled || null,
              resolve: resolve
          });
      });
  };

  function handle(callback) {
      if (state === 'pending') {
          callbacks.push(callback);
          return; } / / ifthenNothing is passed inif(! callback.onResolved) { callback.resolve(value);return;
      }

      var ret = callback.onFulfilled(value);
      callback.resolve(ret);
  }

  function resolve(newValue) {
      if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
          var then = newValue.then;
          if (typeof then= = ='function') {
              then.call(newValue, resolve);
              return;
          }
      }
      state = 'fulfilled';
      value = newValue;
      setTimeout(function () {
          callbacks.forEach(function (callback) {
              handle(callback);
          });
      }, 0);
  }

  fn(resolve);
}Copy the code

Four, four extension points

4.1 Common application scenarios of Promise: Ajax

Use Promise’s knowledge to create a simple wrapper around Ajax. See what it looks like:

// Demo3 PROMISE encapsulates Ajax var URL ='https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10';
function getJSON(url) {
  return new Promise(function(resolve, reject) {
    var XHR = new XMLHttpRequest();
    XHR.open('GET', url, true);
    XHR.send();

    XHR.onreadystatechange = function() {
        if (XHR.readyState == 4) {
            if(XHR.status == 200) { try { var response = JSON.parse(XHR.responseText); resolve(response); } catch (e) { reject(e); }}else {
                reject(new Error(XHR.statusText));
            }
        }
    }
  })
}
getJSON(url).then(resp => console.log(resp));Copy the code

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

When you have an Ajax request whose parameters need to be determined after two or more requests return results, you need to use promise.all to help you navigate this scenario.

4.2 Promise. All

Promise. All takes an array of Promise objects as arguments, and calls then only when the array’s Promise states are Resolved or Rejected.

// demo4 promise.all
var url = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10';
var url1 = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-03-26/2017-06-10';

function renderAll() {
  return Promise.all([getJSON(url), getJSON(url1)]);
}

renderAll().then(function(value) { console.log(value); // Get an array of the values returned by the two interfaces})Copy the code

Results:

clipboard.png

Sometimes, multiple asynchronous tasks are fault tolerant. For example, reading a user’s personal information from two urls at the same time only needs to get the result returned first. In this case, use promise.race ().

4.3 Promise. Race

Like promise. all, promise. race takes an array of Promise objects as arguments, except that the. Then method can be called when one of the Promsie states in the array changes to Resolved or Rejected

// demo5 promise.race
function renderRace() {
  return Promise.race([getJSON(url), getJSON(url1)]);
}

renderRace().then(function(value) {
  console.log(value);
})Copy the code

In this case, the value passed by then() will be the faster interface data returned by the other interface, but the execution will continue, but the result will be discarded.

Results:

clipboard.png

4.4 the Generator function

Generator functions are an implementation of coroutines in ES 6, with the best feature of surrendering execution of functions (suspending execution). Note: You need to turn on the –harmony option in Node to enable Generator functions. The whole Generator function is a encapsulated asynchronous task, or a container for asynchronous tasks. Where asynchronous operations need to be paused, use the yield statement.

Here’s a simple example:

function* gen(x){
    var y = yield x + 2;
    return y;
}

var g = gen(1);
var r1 = g.next(); // { value: 3, done: false }
console.log(r1);
var r2 = g.next() // { value: undefined, done: true }
console.log(r2);Copy the code

Note that the name of the Generator function is preceded by an “*”. In the above code, calling a Generator function returns an internal pointer g. This is the difference between a Generator function and a regular function in that it does not return a result, but a pointer object. Calling the next method on pointer G moves the internal pointer to the first yield statement encountered, as in the previous example, until x+2. In other words, the next method performs the Generator function in stages. Each time the next method is called, an object is returned representing information about the current stage (value and Done properties). The value attribute is the value of the expression following the yield statement and represents the value of the current phase; The done attribute is a Boolean value indicating whether the Generator has completed execution, that is, whether there is another phase.

I am not familiar with Generator functions, I have no experience with them, so I will introduce them here. ES7 async await them. I hope I can get more of them later.

Reference data: 1) www.jianshu.com/p/12b9f73c5… 2) www.jianshu.com/p/fe5f17327… 3) mengera88. Making. IO / 2017/05/18 /… 4) www.cnblogs.com/nullcc/p/58…