要了解 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 start
script end
process.nextTick
promise
promise-then
setTimeout 0
setImmediate
setTimeout-delay 100ms promise
setTimeout-delay 100ms promise.then
*/
参考链接
https://nodejs.dev/en/learn/t…
文章首发于 IICOOM- 集体博客《JavaScript 中的异步、同步》