JavaScript的同步与异步
- 同步: 在一个线程上同一时间只能做一件事情,当情事情做完才能进行下一个任务。
- 异步: 在主栈中执行一个任务,但是发现这个任务是一个异步的操作,会将它放到异步任务队列中。
异步任务又分为宏任务与微任务:
- 宏任务: 定时器
setTimeout
,setInterval
,事件绑定
,回调函数
,node中的fs模块
- 微任务:
new Promise().then(回调)
,process.nextTick()
,async await
执行栈与任务队列
1)执行栈:从名字可以看出,执行栈使用到的是数据结构中的栈结构, 它是一个存储函数调用的栈结构,遵循先进后出的原则。它主要负责跟踪所有要执行的代码。 每当一个函数执行完成时,就会从堆栈中弹出(pop)该执行完成函数;如果有代码需要进去执行的话,就进行 push 操作。
2)任务队列: 从名字中可以看出,任务队列使用到的是数据结构中的队列结构,它用来保存异步任务,遵循先进先出的原则。它主要负责将新的任务发送到队列中进行处理。
执行顺序
JavaScript在执行代码时,会将同步的代码按照顺序排在执行栈中,然后依次执行里面的函数。当遇到异步任务时,就将其放入任务队列中,等待当前执行栈所有同步代码执行完成之后,就会从异步任务队列中取出已完成的异步任务的回调并将其放入执行栈中继续执行,如此循环往复,直到执行完所有任务
先执行同步任务,执行完接着执行微任务,最后执行宏任务。这个过程会不断重复
下面看经典面试题
console.log("script start");
async function async1() {
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2 end");
}
async1();
setTimeout(function () {
console.log("setTimeout");
}, 0);
new Promise((resolve) => {
console.log("Promise");
resolve();
})
.then(function () {
console.log("promise1");
})
.then(function () {
console.log("promise2");
});
console.log("script end");
下面是结果:
解释:
在 JavaScript 中,Promise 构造函数内部的代码会在 Promise 实例被创建时立即执行,但是 Promise 内部的代码仍然属于异步任务。这是因为 Promise 的设计目的之一是处理异步操作,它允许你以一种更结构化的方式处理异步代码。
虽然 Promise 构造函数内的代码会在 Promise 实例被创建时立即执行,但它仍然是一个异步操作。具体来说:
new Promise((resolve) => {
console.log("Promise");
resolve();
})
这段代码中的 console.log("Promise");
将会在 Promise 实例被创建时执行,但不会阻塞后续的代码执行。当调用 .then()
方法时,传入的回调函数会在当前执行栈执行完毕后、事件循环的下一个循环中执行。
尽管 Promise 构造函数内的代码会立即执行,但它仍然属于异步任务,不会阻塞后续代码的执行。因此可以将其看作是一个在事件循环中即将执行的任务,而不是同步代码。
而async
函数内部使用 await
关键字来暂停执行,等待一个 Promise 的解决。当遇到 await
时,async
函数会在此处暂停,并将后续代码添加到微任务队列中,然后立即返回一个暂停的 Promise。这意味着 async
函数本身不会立即被推到微任务队列中。
考虑以下代码:
async function async1() {
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2 end");
}
async1();
console.log("async1 function called");
在这个例子中,当调用 async1()
时,以下操作发生:
async1()
函数开始执行,遇到await async2()
,这会导致async2()
函数立即执行,输出 "async2 end",然后暂停async1()
函数的执行。async2()
函数执行完毕后,它返回一个已解决的 Promise,此时async1()
函数的暂停状态被解除。- 此时,"async1 end" 被添加到微任务队列中。
console.log("async1 function called");
继续执行,输出 "async1 function called"。
现在,事件循环会在当前执行栈执行完毕后,检查微任务队列。然后,"async1 end" 会被从微任务队列中取出并输出。
所以,async
函数内部的代码会被划分成几个微任务,但 async
函数本身不会立即被推到微任务队列中。它会在遇到 await
关键字时被暂停,然后在后续的 Promise 解决后,将微任务添加到队列中。