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
为何是这种执行结果?浏览器
这就要说到js的执行机制:事件循环(event loop)!网络
当JS解析执行时,会被引擎分为两类任务,同步任务(synchronous) 和 异步任务(asynchronous)。异步
对于同步任务来讲,会被推到执行栈按顺序去执行这些任务。
对于异步任务来讲,当其能够被执行时,会被放到一个 任务队列(task queue) 里等待JS引擎去执行。async
当执行栈中的全部同步任务完成后,JS引擎才会去任务队列里查看是否有任务存在,并将任务放到执行栈中去执行,执行完了又会去任务队列里查看是否有已经能够执行的任务。这种循环检查的机制,就叫作事件循环(Event Loop)。函数
对于任务队列,实际上是有更细的分类。其被分为 微任务(microtask)队列 & 宏任务(macrotask)队列
宏任务: setTimeout、setInterval等,会被放在宏任务(macrotask)队列。
微任务: Promise的then、Mutation Observer等,会被放在微任务(microtask)队列。
1.首先执行执行栈里的任务。
2.执行栈清空后,检查微任务(microtask)队列,将可执行的微任务所有执行。
3.取宏任务(macrotask)队列中的第一项执行。
4.回到第二步。oop
如今咱们知道了为何定时器会晚于promise执行了。下面咱们讨论一下微任务的几种实现状况:Promsie、Generator、async/await。spa
===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命令的语法糖。