要了解JS中的异步、同步,须要先理解JS代码的执行过程和Event Loop。
JavaScript代码的执行过程
程序须要执行的操作都会被放入Call Stack(A LIFO (Last In, First Out) Stack),先进后出的数据结构。
const bar = () => console.log('bar')const baz = () => console.log('baz')const foo = () => { console.log('foo') bar() baz()}foo()
当这段代码执行时,foo()会首先被调用。在foo()外部先调用了bar(),而后调用了baz()。在这个时刻Call Stack 是上面的样子:
执行每一步操作都有新的操作压入栈顶,执行结束就从栈顶弹出,直到整个栈变为Empty。
Event Loop 起到的作用就是每次迭代都回去查看Call Stack中是否有待执行的指令,而后去执行它。
JavaScript中的异步、同步
同步执行代码
大多数的状况下,JavaScript是以同步的模式执行代码:
let log = console.log;let a = 5;let b = 50;let a1 = function() { return 5 }let b1 = function() { return 50 }log(a1())log(a2())let a2 = function(num) { return 5*num }let b2 = function() { return 50 }log(a2(b2))// 打印出:// 5// 50// 250
异步执行的代码
setTimeout, callbacks, Promise, fetch, ajax, filesystem interaction, database calls, DOM event listener
上边这些状况代码将是异步执行。
起因是代码执行到这些办法时,是不确定对应的操作多久能够执行结束,所以会持续向下执行。
思考上面的情景:
let a3 = 100;setTimeout(function() { a3++ }, 0);log(a3)setTimeout(function() { log(a3) }, 0);// 打印出// 100// 101
同步模式的代码被放入 Call Stack 中执行,异步代码放到了一个队列中(Message Queue)。Event Loop 会优先解决 Call Stack 中的工作,当 Call Stack 为空,就会把 Message Queue 中的工作拿进去执行。
ES6 Job Queue
ECMAScript 2015 引入了 Job Queue 的概念,被 Promises 应用。它会使异步办法的后果尽快执行,而不是放到 Call Stack 的最初执行。
Promise是异步的一个十分好的实现:
const bar = () => console.log('bar')const baz = () => console.log('baz')const foo = () => { console.log('foo') setTimeout(bar, 0) new Promise((resolve, reject) => resolve('should be right after baz, before bar') ).then(resolve => console.log(resolve)) baz()}foo()// foo// baz// should be right after baz, before bar// bar
能够看到Promise会先于setTimeout执行。
看一下这段代码,你判断一下它的执行程序是怎么样的?
console.log(1);setTimeout(() => console.log(2));Promise.resolve().then(() => console.log(3));Promise.resolve().then(() => setTimeout(() => console.log(4)));Promise.resolve().then(() => console.log(5));setTimeout(() => console.log(6));console.log(7);// 1// 7// 3// 5// 2// 6// 4
Nodejs Process.NextTick() 和 SetImmediate()
Process.NextTick() 会在事件循环的一个周期的最初执行,通过这个办法能够实现,把一个异步办法尽快执行而不是放入异步队列中。
Process.NextTick 回调函数会被增加到 Process.NextTick 队列,Promise.Then() 会被增加到 Promises 微工作队列(Microtask Queue),SetTimeout, SetImmediate 会被增加到宏工作队列(Macrotask Queue)。
SetTimeout() 延时0ms的异步与 SetImmediate()很类似,它们都是在下一次事件循环执行。
事件循环会先执行 Process.NextTick 队列,而后执行 Promises 微工作队列,而后是 宏工作队列。
console.log('script start');// 异步Promise.resolve().then(function() { console.log('promise');}).then(function() { console.log('promise-then');});// 异步setImmediate(function() { console.log('setImmediate')})// 异步setTimeout(function() { console.log('setTimeout 0')}, 0)// 异步setTimeout(function() { return new Promise(resolve => { console.log('setTimeout-delay 100ms promise') resolve() }).then(res => { console.log('setTimeout-delay 100ms promise.then') })}, 100)process.nextTick(function() { console.log('process.nextTick')})console.log('script end');/*script startscript endprocess.nextTickpromisepromise-thensetTimeout 0setImmediatesetTimeout-delay 100ms promisesetTimeout-delay 100ms promise.then*/
参考链接
https://nodejs.dev/en/learn/t...
文章首发于 IICOOM-集体博客 《JavaScript中的异步、同步》