从microTask、macroTask到async-await简单了解

经常在网上看到有关microTaskmacroTask的字眼,限制于当初学习前端是看视频起的步(这里走了很多弯路),后来开始阅读书籍,虽补充了许多基础知识,但当我看到micro和macro还是一头雾水,wtf?这是什么知识,我怎么没有接触过。

网上查阅一番,发现其实是setTimeout等异步方式的分类。

大致分类如下:

macroTask(宏任务) microTask(微任务)
setTimeout process.nextTick(nodejs)
setInterval Promise
setImmediate MutationObserver
I/O
UI Rendering

从上面表格来看,哎呀,全特么都是一些熟悉的小伙伴呀,那让我们看看macroTaskmicroTask间有啥区别吧。

基础

这里不得不提下eventloop,因为JS是单线程的,所以如果遇到执行时间过长的任务,我们往往会选择使用异步方式,当任务执行好了,再进行处理,从而不影响其他代码的执行。例如:

1
2
3
4
5
console.log(1);
setTimeout(() => {
console.log(2);
}, 100);
console.log(3);

这里我们的执行过程是,主线程执行输出1,遇到setTimeout语句,在所设置的时间(100ms)过后把回调添加到事件队列(eventloop)中,主线程输出3,这时发现主线程已经空闲了,便去询问事件队列中是否有等待执行的异步事件,如果有则取到主线程中执行,就这样周而复始,直到代码全部执行完。

eventloop

上图中,主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在”任务队列”中加入各种事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取”任务队列”,依次执行那些事件所对应的回调函数 - from 阮一峰博客

更多eventloop资料

  1. 什么是 Event Loop?
  2. JavaScript 运行机制详解:再谈Event Loop

上面讲到eventloop其实是一个任务队列,但是对于更好地理解macroTask和microTask,我一般理解成有两个任务队列。一个是接收宏任务的macroQueue,另一个是接收微任务的microQueue。当主线程空闲后,首先会去执行microQueue,然后再去执行macroQueue

题目

1
2
3
4
5
6
7
8
console.log(1);
setTimeout(() => {
console.log(2);
}, 0);
Promise.resolve().then(() => {
console.log(3);
});
console.log(4);

Q: 请给出上例代码的输出结果?

A: 1、4、3、2

大概的原理便是上面提到的 当主线程空闲后,首先会去执行microQueue,然后再去执行macroQueue

async-await

ES201x的新语法,号称”异步的终极方案”, 我们使用await中,常常配合使用着Promise。用起来很爽,但是对于原理还是不清楚,特地简单的了解了一下。

让我们先做几道题目压压惊.

题目

1
2
3
4
5
6
7
8
9
10
11
function bb() {
console.log('This is bb');
return Promise.resolve(4);
}
console.log(1);
(async () => {
console.log(2);
const aa = await bb();
console.log(aa);
})();
console.log(3);

Q: 请给出输出结果?

A: 1、2、This is bb、3、4

从输出结果,我们可以猜测,await在执行完表达式bb()结果后,会把当前执行权释放给主线程,这里主线程输出3,当主线程空闲后,再获取到控制权,这时bb()的返回值是4,赋值给aa,然后输出4。

使用generator实现async-await

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 让我们使用generator来实现async-await的功能。
const wrapAsync = function (generatorFn) {
return function (...args) {
return new Promise((resolve, reject) => {
const gen = generatorFn(...args);
const go = (result) => {
if (result.done) {
resolve(result.value);
return;
}
if (result.value instanceof Promise) {
result.value.then(value => {
go(gen.next(value));
});
} else {
go(gen.next(result.value));
}
}

try {
go(gen.next());
} catch (err) {
reject(err);
}
})
}
};

// Test
const getData = (name) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('My name is ' + name)
}, 100) // 模拟异步获取数据
})
}

const run = wrapAsync(function * (lastName) {
const data1 = yield getData('Zhou ' + lastName)
const data2 = yield getData('Z ' + lastName)
const data3 = yield 123
return [data1, data2, data3]
})

run('yutao').then((val) => {
console.log(val) // => [ 'My name is Zhou yutao', 'My name is Z yutao', 123 ]
})

由于时间仓促,难免有遗漏和错误之处,如若发现还请指出,谢谢。

推荐文章