乐趣区

javascript-异步编程

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

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

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('执行同步任务');

执行结果如下:

为什么是这种执行结果?

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

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

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

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

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

现在我们知道了为什么 定时器 会晚于 promise 执行了。下面我们讨论一下微任务的几种实现情况:Promsie、Generator、async/await

===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 命令的语法糖。

退出移动版