javascript 异步编程

javascript 是单线程执行的,由js文件自上而下依次执行。即为同步执行,如果有网络请求或者定时器等业务时,不能让浏览器傻傻等待到结束后再继续执行后面的js吧!因此js设计了异步模式!javascript

下面是一个常见的定时器与promise的问题:java

setTimeout(() => {
    console.log('我是第一个宏任务');
    Promise.resolve().then(() => {
        console.log('我是第一个宏任务里的第一个微任务');
    });
    Promise.resolve().then(() => {
        console.log('我是第一个宏任务里的第二个微任务');
    });
}, 0);

setTimeout(() => {
    console.log('我是第二个宏任务');
}, 0);

Promise.resolve().then(() => {
    console.log('我是第一个微任务');
});

console.log('执行同步任务');

执行结果以下:promise

clipboard.png

为何是这种执行结果?浏览器

这就要说到js的执行机制:事件循环(event loop)!网络

当JS解析执行时,会被引擎分为两类任务,同步任务(synchronous)异步任务(asynchronous)异步

对于同步任务来讲,会被推到执行栈按顺序去执行这些任务。
对于异步任务来讲,当其能够被执行时,会被放到一个 任务队列(task queue) 里等待JS引擎去执行。async

当执行栈中的全部同步任务完成后,JS引擎才会去任务队列里查看是否有任务存在,并将任务放到执行栈中去执行,执行完了又会去任务队列里查看是否有已经能够执行的任务。这种循环检查的机制,就叫作事件循环(Event Loop)。函数

clipboard.png

对于任务队列,实际上是有更细的分类。其被分为 微任务(microtask)队列 & 宏任务(macrotask)队列
宏任务: setTimeout、setInterval等,会被放在宏任务(macrotask)队列。
微任务: Promise的then、Mutation Observer等,会被放在微任务(microtask)队列。
1.首先执行执行栈里的任务。
2.执行栈清空后,检查微任务(microtask)队列,将可执行的微任务所有执行。
3.取宏任务(macrotask)队列中的第一项执行。
4.回到第二步。oop

如今咱们知道了为何定时器会晚于promise执行了。下面咱们讨论一下微任务的几种实现状况:Promsie、Generator、async/awaitspa

===Promsie===

Promise对象是一个构造函数,用来生成Promise实例;

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操做成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

Promise 新建后就会当即执行。

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hi!');

// Promise
// Hi!
// resolved

Promise 对象的错误具备“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误老是会被下一个catch语句捕获。

// bad
promise
  .then(function(data) {
    // success
  }, function(err) {
    // error
  });

// good
promise
  .then(function(data) { //cb
    // success
  })
  .catch(function(err) {
    // error
  });

跟传统的try/catch代码块不一样的是,若是没有使用catch方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。

===Generator===

Generator 函数是一个状态机,封装了多个内部状态。
执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,仍是一个遍历器对象生成函数。返回的遍历器对象,能够依次遍历 Generator 函数内部的每个状态。

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();

===async===

async 函数是什么?一句话,它就是 Generator 函数的语法糖。

(1)内置执行器。

Generator 函数的执行必须靠执行器,因此才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数如出一辙,只要一行。

asyncReadFile();
上面的代码调用了asyncReadFile函数,而后它就会自动执行,输出最后结果。这彻底不像 Generator 函数,须要调用next方法,或者用co模块,才能真正执行,获得最后结果。

(2)更好的语义。

async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操做,await表示紧跟在后面的表达式须要等待结果。

(3)更广的适用性。

co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,能够是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成当即 resolved 的 Promise 对象)。

(4)返回值是 Promise。

async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你能够用then方法指定下一步的操做。

进一步说,async函数彻底能够看做多个异步操做,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。

相关文章
相关标签/搜索