What exactly is asynchronous programming?

  In our work and learning, is full of asynchronous figure, in the end what is asynchronous, what is the asynchronous programming, why use asynchronous programming, as well as the classic asynchronous programming which, what is the scene at work, we little deeper to learn.

  What is asynchronous programming?

  We need to look at what is asynchronous programming, why asynchronous programming.

  Let me talk about a concept of asynchronous and synchronous. Before introducing asynchronous, look at the so-called synchronous programming, is the computer according to the order line by line through the code, the current code time-consuming task execution block the follow-up code.

  Synchronous programming, i.e., a typical request - response model, when the request is a function or method call, wait for its response back, and then execute the subsequent code.

  In general, the synchronization program code sequentially performed sequentially, to ensure good execution of the program, but in some scenarios, such as reading the file contents, or requests the server interface data, the data needs to follow up what is returned , and the read file request interface until the data is returned to this process takes time, the worse the network, the longer the time-consuming.

  If implemented in synchronous programming mode, waiting to return data from this period, JavaScript can not handle other tasks, interaction at this time of the page, scrolling any operation will also be blocked, which is obviously unfriendly and unacceptable, asynchronous programming which is required to show their talents scene.

  We want to render the page request data by Ajax, this is a very common rendering pages in our way among the front. Each page will have this basic process. What happens here in the requested page with a synchronized manner? Browser lock, you can not perform other operations. And every time to send a new request, the browser will be locked, the user experience is poor.
What exactly is asynchronous programming?
  In the browser synchronous execution will be like this above, Task 1 to finish in order to do the task 2, task 2 will do the task done 3. There reflects the characteristics of synchronous sequential programming. Not only 1,2,3 1,3,2. But our code logic can exist in the process of execution of multiple tasks simultaneously. In our life, cooking and water heating can be done at the same time, we are also in need of such programming logic.
What exactly is asynchronous programming?
  How in the computer concept of threads, what this means, each thread do a thing like this.

  You can perform different tasks in different threads.

  But our JavaScript is single-threaded, single-threaded here, the emphasis is single-threaded execution threads. There are also behind the thread pool, thread pool threads and the concept here is not to say. For the students can look at the operating system-related books.

  JavaScript language execution environment is single-threaded, single-threaded during program execution, the program path to go down in a sequential row order, must deal with in front of, behind will be executed.

  But we also need to perform this way is similar to multi-threading mechanism. But JavaScript is single-threaded, we need asynchronous execution, asynchronous execution will make the concurrent execution of multiple tasks.

  Parallel and concurrent. The previously mentioned multi-threaded tasks can be executed in parallel, while single-threaded asynchronous JavaScript programming can multi-task concurrent execution, here it is necessary to explain the difference between parallel and concurrent.

  Parallel, refers to multi-task at the same time within the same time. Side cooking, water heating side, can be complicated by simultaneous, it refers to the same time period, the multi-task at the same time, but at some point, only to perform a certain task. Eat and drink, at the same time point can only drink water and eat.

  Then talk about asynchronous mechanism

  Concurrency model

  Currently, we already know that when asynchronous JavaScript execution task, do not need to wait for a response is returned, you can continue to perform other tasks, and in the response comes back, it will be notified callback or event handler. So exactly how it all done so, on what rule or order work? Next we need to answer this question. Essentially there is no difference between the callback and event handlers, just in different situations, different name.

  As already mentioned, asynchronous JavaScript programming allows multiple tasks can execute concurrently, and the realization of this basic function is to have a JavaScript-based concurrency model event loop.

  Stacks and queues

  Before the introduction of JavaScript concurrency model, briefly explain the difference between stacks and queues:

  Heap (heap): a memory region is not blocked, typically stored object (reference type);

  Stack (stack): LIFO sequence of data structures, typically stored function parameters and values ​​of the basic variable types (access by value);

  Queue (queue): FIFO sequentially stored data structure.

  Event loop (EventLoop): JavaScript engine is responsible for parsing, execute JavaScript code, but it can not be run separately, usually had to have a hosting environment, such as the general browser or Node server, before it comes to the single-threaded text refers to the host environment create a single thread, providing a mechanism to invoke the JavaScript engine complete schedule multiple JavaScript code blocks, execution (yes, JavaScript code is executed in blocks), this mechanism is called event loop (EventLoop).

  JavaScript execution two structures present in the environment need to know:

  Message Queue (messagequeue), also called task queue (taskqueue): store pending messages and the corresponding callback function or event handler;

  The execution stack (executioncontextstack), also called the execution context stack: JavaScript execution stack, by definition, is composed by the execution context when the function calls, create and insert an execution context, commonly referred to as the execution stack frame (frame), stores function parameters and local variables, when execution of the function execution stack eject the frame;

  Note: For global code, because all the code is executed in the global context, the execution stack is always easy to understand the global context, until all the code is finished, quit the global context execution stack, the stack is empty; that is, a global the first context is pushed onto the stack, the stack last.

  task

  Before the event loop analysis process, first elaborated two concepts helps to understand the event loop: synchronous and asynchronous tasks task.

  Task well understood, JavaScript code that is executed in the task, the task is called a code block or a function, usually divided into function or purpose, such as to complete an addition calculation, a complete ajax request; it is natural and is divided into synchronization task asynchronous tasks. Synchronization task is continuous obstruction; and asynchronous task is not continuous, non-blocking, asynchronous events and include callbacks, when we talk about the implementation of asynchronous tasks, it usually refers to the implementation of the callback function.

  Event Loop Process

  About the event loop process breaks down as follows:

  JavaScript hosting environment when creating a thread is created heap (heap) and stack (stack), JavaScript objects stored in the heap, stack storage within the execution context is;

  Within the context of the implementation of synchronous task order execution stack, namely the implementation of complete de-stacked, and when the asynchronous task execution, the asynchronous task into a wait state (do not stack), notify the threads: When the trigger event (or the asynchronous operation when the response is returned), the need to insert an event message to a message queue;

  When the event occurs, or in response to return, the thread is inserted into the event message queue to the message (including events and callback);

  When the synchronization task is completed within the stack, the thread is removed from the message queue of an event message, which corresponds to an asynchronous task (function) stack, to the callback, the callback if unbound, the message is discarded, the task finishes reverse stack;

  When a thread is idle (that is, the execution stack empty) continue to pull message queue the next round of news (nexttick, the event loop circulation once called a tick).

  Using the code can be described as follows:

  vareventLoop = [];

  var event;

  var i = eventLoop.length - 1; // LIFO

  while(eventLoop[i]) {

  event = eventLoop[i--];

  if (event) {// event callbacks exist

  event();

  }

  // event otherwise the message is discarded

  }

  Here note is waiting for the next event messages are synchronized process.

  Concurrent with the event loop model

  varele = document.querySelector('body');

  function clickCb(event) {

  console.log('clicked');

  }

  function bindEvent(callback) {

  ele.addEventListener('click', callback);

  }

  bindEvent (clickCb);

  For the code above we can construct the following concurrency model:
What exactly is asynchronous programming?
  As shown above, when the execution stack sync block sequentially executing the until met asynchronous tasks, the asynchronous task into a wait state, the notification thread, asynchronous event is triggered, to the message queue is inserted into an event message; when the execution stack after the subsequent synchronization code execution, reading a message queue, to give a message, the message corresponding to the asynchronous task stack and performs callback function; an event cycle is completed, i.e., the processing of an asynchronous task.

  JS Asynchronous There are several?

  JS pretty much in asynchronous operation, the common following points:

  setTimeout(setInterval)

  AJAX

  Promise

  Generator

  setTimeout

  setTimeout(

  function() {

  console.log("Hello!");

  },1000);

  setTimout (setInterval) is not performed immediately, this code means that, after the other 1s, this function is added to the task queue, the task queue if no other task, executes output 'Hello'.

  varouterScopeVar;

  helloCatAsync ();

  alert(outerScopeVar);

  functionhelloCatAsync () {

  setTimeout(function() {

  outerScopeVar = 'hello';

  }, 2000);

  }

  Performing the above code, the output is found outerScopeVar undefined, instead of hello. This is so because a value returned in asynchronous code synchronization process is impossible to use, because console.log (outerScopeVar) is a synchronization code, will be performed after the implementation setTimout.

  helloCatAsync(function(result){

  console.log(result);

  });

  functionhelloCatAsync(callback) {

  setTimeout(

  function() {

  callback('hello')

  }

  , 1000)

  }

  把上面代码改成,传递一个callback,console输出就会是hello。

  AJAX

  varxhr = new XMLHttpRequest();

  xhr.onreadystatechange= function() {

  if ((xhr.status >= 200 && xhr.status < 300) ||xhr.status == 304 ) {

  console.log(xhr.responseText);

  } else {

  console.log( xhr.status);

  }

  }

xhr.open('GET','url', false);

  xhr.send();

  上面这段代码,xhr.open中第三个参数默认为false 异步执行,改为true 时为同步执行。

  Promise规范简述

  promise是一个拥有then 方法的对象或函数。一个promise有三种状态pending, rejected, resolved状态一旦确定就不能改变,且只能够由pending状态变成rejected或者resolved状态,reject和resolved状态不能相互转换。

  当promise执行成功时,调用then方法的第一个回调函数,失败时调用第二个回调函数。promise实例会有一个then方法,这个then方法必须返回一个新的promise。

  基本用法

  //异步操作放在Promise构造器中

  constpromise1 = new Promise((resolve) => {

  setTimeout(() => {

  resolve('hello');

  }, 1000);

  });

  //得到异步结果之后的操作

  promise1.then(value=> {

  console.log(value, 'world');

  },error =>{

  console.log(error, 'unhappy')

  });

  异步代码,同步写法

  asyncFun()

  .then(cb)

  .then(cb)

  .then(cb)

  promise以这种链式写法,解决了回调函数处理多重异步嵌套带来的回调地狱问题,使代码更加利于阅读,当然本质还是使用回调函数。

  异常捕获

  前面说过如果在异步的callback函数中也有一个异常,那么是捕获不到的,原因就是回调函数是异步执行的。我们看看promise是怎么解决这个问题的。

  asyncFun(1).then(function(value) {

  throw new Error('出错啦');

  },function (value) {

  console.error(value);

  }).then(function(value) {

  },function (result) {

  console.log('有错误',result);

  });

  其实是promise的then方法中,已经自动帮我们trycatch了这个回调函数,实现大致如下:

  Promise.prototype.then= function(cb) {

  try{

  cb()

  }catch (e) {

  // todo

  reject(e)

  }

  }

  then方法中抛出的异常会被下一个级联的then方法的第二个参数捕获到(前提是有),那么如果最后一个then中也有异常怎么办。

  Promise.prototype.done= function (resolve, reject) {

  this.then(resolve, reject).catch(function (reason) {

  setTimeout(() => {

  throw reason;

  }, 0);

  });

  };

  asyncFun(1).then(function (value) {

  throw new Error('then resolve回调出错啦');

  }).catch(function (error) {

  console.error(error);

  throw new Error('catch回调出错啦');

  }).done((reslove, reject) => {});

  我们可以加一个done方法,这个方法并不会返回promise对象,所以在此之后并不能级联,done方法最后会把异常抛到全局,这样就可以被全局的异常处理函数捕获或者中断线程。这也是promise的一种最佳实践策略,当然这个done方法并没有被ES6实现,所以我们在不适用第三方Promise开源库的情况下就只能自己来实现了。为什么需要这个done方法。

  constasyncFun = function (value) {

  return new Promise(function (resolve, reject) {

  setTimeout(function () {

  resolve(value);

  }, 0);

  })

  };

  asyncFun(1).then(function(value) {

  throw new Error('then resolve回调出错啦');

  });

  (node:6312)UnhandledPromiseRejectionWarning: Unhandled promise rejection(rejection id: 1): Error: then resolve回调出错啦

  (node:6312)[DEP0018] DeprecationWarning: Unhandled promise rejections aredeprecated. In the future, promise rejections that are not handledwill terminate the Node.js process with a non-zero exit code

  我们可以看到JavaScript线程只是报了一个警告,并没有中止线程,如果是一个严重错误如果不及时中止线程,可能会造成损失。

  局限

  promise有一个局限就是不能够中止promise链,例如当promise链中某一个环节出现错误之后,已经没有了继续往下执行的必要性,但是promise并没有提供原生的取消的方式,我们可以看到即使在前面已经抛出异常,但是promise链并不会停止。虽然我们可以利用返回一个处于pending状态的promise来中止promise链。

  constpromise1 = new Promise((resolve) => {

  setTimeout(() => {

  resolve('hello');

  }, 1000);

  });

  promise1.then((value)=> {

  throw new Error('出错啦!');

  }).then(value=> {

  console.log(value);

  },error=> {

  console.log(error.message);

  return result;

  }).then(function() {

  console.log('DJL箫氏');

  });

  特殊场景

  当我们的一个任务依赖于多个异步任务,那么我们可以使用Promise.all,当我们的任务依赖于多个异步任务中的任意一个,至于是谁无所谓,Promise.race

  上面所说的都是ES6的promise实现,实际上功能是比较少,而且还有一些不足的,所以还有很多开源promise的实现库,像q.js等等,它们提供了更多的语法糖,也有了更多的适应场景。

  核心代码

  vardefer = function () {

  var pending = [], value;

  return {

  resolve: function (_value) {

  value = _value;

  for (var i = 0, ii = pending.length; i < ii; i++) {

  var callback = pending[i];

  callback(value);

  }

  pending = undefined;

  },

  then: function (callback) {

  if (pending) {

  pending.push(callback);

  } else {

  callback(value);

  }

  }

  }

  };

  当调用then的时候,把所有的回调函数存在一个队列中,当调用resolve方法后,依次将队列中的回调函数取出来执行

  varref = function (value) {

  if (value && typeof value.then === "function")

  return value;

  return {

  then: function (callback) {

  return ref(callback(value));

  }

  };

  };

  这一段代码实现的级联的功能,采用了递归。如果传递的是一个promise那么就会直接返回这个promise,但是如果传递的是一个值,那么会将这个值包装成一个promise。

  下面promise 和ajax 结合例子:

  functionajax(url) {

  return new Promise(function(resolve, reject) {

  var xhr = new XMLHttpRequest();

  xhr.onreadystatechange = function() {

  if ((xhr.status >= 200 && xhr.status < 300) ||xhr.status == 304 ) {

  resovle(xhr.responseText);

  } else {

  reject( xhr.status);

  }

  }

xhr.open('GET', url, false);

  xhr.send();

  });

  }

  ajax('/test.json')

  .then(function(data){

  console.log(data);

  })

  .cacth(function(err){

  console.log(err);

  });

  generator

  基本用法

  function* gen (x) {

  const y = yield x + 2;

  // console.log(y); // 猜猜会打印出什么值

  }

  constg = gen(1);

  console.log('first',g.next()); //first { value: 3, done: false }

  console.log('second',g.next()); // second { value: undefined, done: true }

  通俗的理解一下就是yield关键字会交出函数的执行权,next方法会交回执行权,yield会把generator中yield后面的执行结果,带到函数外面,而next方法会把外面的数据返回给generator中yield左边的变量。这样就实现了数据的双向流动。

  generator实现异步编程

  我们来看generator如何是如何来实现一个异步编程(*)

  constfs = require('fs');

  function* gen() {

  try {

  const file = yield fs.readFile;

  console.log(file.toString());

  } catch(e) {

  console.log('捕获到异常',e);

  }

  }

  //执行器

  constg = gen();

g.next().value('./config1.json',function (error, value) {

  if(error) {

  g.throw('文件不存在');

  }

g.next(value);

  });

  那么我们next中的参数就会是上一个yield函数的返回结果,可以看到在generator函数中的代码感觉是同步的,但是要想执行这个看似同步的代码,过程却很复杂,也就是流程管理很复杂。

  扩展:

  异步编程与多线程的区别

  共同点:

  异步和多线程两者都可以达到避免调用线程阻塞的目的,从而提高软件的可响应性

  不同点:

  线程不是一个计算机硬件的功能,而是操作系统提供的一种逻辑功能,线程本质上是进程中一段并发运行的代码,所以线程需要操作系统投入CPU资源来运行和调度。多线程的优点很明显,线程中的处理程序依然是顺序执行,符合普通人的思维习惯,所以编程简单。但是多线程的缺点也同样明显,线 程的使用(滥用)会给系统带来上下文切换的额外负担。并且线程间的共享变量可能造成死锁的出现

  异步操作无须额外的线程负担,并且使用回调的方式进行处理,在设计良好的情况下,处理函数可以不必使用共享变量(即使无法完全不用,最起码可以减少共享变量的数量),减少了死锁的可能。当然异步操作也并非完美无暇。编写异步操作的复杂程度较高,程序主要使用回调方式进行处理,与普通人的思维方式有些出入,而且难以调试。

  这里有一个疑问。异步操作没有创建新的线程,我们一定会想,比如有一个文件操作,大量数据从硬盘上读取,若使用单线程的同步操作自然要等待会很长时间,但是若使用异步操作的话,我们让数据读取异步进行,二线程在数据读取期间去干其他的事情,我们会想,这怎么行呢,异步没有创建其他的线程,一个线程去干其他的事情去了,那数据的读取异步执行是去由谁完成的呢?实际上,本质是这样的。

  熟悉电脑硬件的朋友肯定对DMA这个词不陌生,硬盘、光驱的技术规格中都有明确DMA的模式指标,其实网卡、声卡、显卡也是有DMA功能的。DMA就是直接内存访问的意思,也就是说,拥有DMA功能的硬件在和内存进行数据交换的时候可以不消耗CPU资源。只要CPU在发起数据传输时发送一个指令,硬件就开始自己和内存交换数据,在传输完成之后硬件会触发一个中断来通知操作完成。这些无须消耗CPU时间的I/O操作正是异步操作的硬件基础。所以即使在DOS这样的单进程(而且无线程概念)系统中也同样可以发起异步的DMA操作。

  即CPU在数据的长时间读取过程中,只需要做两件事,第一发布指令,开始数据交换;第二,交换结束,得到指令,CPU再进行后续操作。而中间读取数据漫长的等待过程,CPU本身就不需要参与,顺序执行就是我不参与但是我要干等着,效率低下;异步执行就是,我不需要参与那我就去干其他事情去了,你做完了再通知我就可以了(回调)。

  但是你想一下,如果有一些异步操作必须要CPU的参与才能完成呢,即我开始的那个线程是走不开的,这该怎么办呢,在.NET中,有线程池去完成,线程池会高效率的开启一个新的线程去完成异步操作,在python中这是系统自己去安排的,无需人工干预,这就比自己创建很多的线程更加高效。

  总结:

  “多线程”,第一、最大的问题在于线程本身的调度和运行需要很多时间,因此不建议自己创建太大量的线程;第二、共享资源的调度比较难,涉及到死锁,上锁等相关的概念。

  “异步”,异步最大的问题在于“回调”,这增加了软件设计上的难度。

  In the actual design, we can combine the two:

  When the need to perform I / O operations, the use of threads using asynchronous operations than + synchronous I / O operation is more suitable. I / O operation includes not only direct file read and write network also includes calls to the database operations, WebService, HttpRequest and .netRemoting such as cross-process. Asynchronous IO is particularly suitable for most intensive applications.

  The scope of application of the thread is the kind of operation takes a long time CPU occasions, such as time-consuming graphics processing and algorithm execution. But often due to the use of threaded programming simple and consistent with customary, so many of my friends tend to perform lengthy I / O operations using threads. So that only a few concurrent operations when it innocuous, if you need to handle a large number of concurrent operations is inappropriate.

Guess you like

Origin blog.51cto.com/13409950/2461631