JavaScript中的任务可以分为同步(Synchronous)和异步(Asynchronous)两种。
同步任务(Synchronous Tasks):在执行过程中,同步任务会按顺序一个接一个地执行。当一个任务正在执行时,其他任务必须等待,直到当前任务完成,然后才会执行下一个任务。例如,一个简单的加法运算就是一个同步任务。
console.log(1 + 2); // 输出:3 |
异步任务(Asynchronous Tasks):与同步任务不同,异步任务不会阻塞代码的执行。它们会在后台运行,当异步任务完成时,它会触发一个回调函数或者Promise。例如,JavaScript的setTimeout函数就是一个异步任务。
setTimeout(function() { | |
console.log('Hello, world!'); | |
}, 1000); // 在1秒后打印"Hello, world!" |
在JavaScript中,许多内置函数(如Ajax请求、读取文件、定时器等)都是异步的。为了处理异步操作,JavaScript提供了回调函数、Promise、async/await等机制。
-
回调函数:当异步任务完成后,回调函数会被调用。
fs.readFile('/etc/passwd', 'utf-8', function(err, data) { | |
if (err) throw err; | |
console.log(data); | |
}); |
-
Promise:Promise是一种更高级的异步处理模式,它可以在异步操作完成时返回一个结果,或者在失败时返回一个错误。
let promise = new Promise(function(resolve, reject) { | |
// 异步操作,可能需要一些时间 | |
setTimeout(() => resolve("Success!"), 1000); // 在1秒后resolve | |
}); | |
promise.then(result => console.log(result)); // 打印"Success!" |
-
async/await:async关键字标记一个函数为异步函数,await关键字用于等待Promise的完成。
async function asyncFunc() { | |
let promise = new Promise((resolve, reject) => { | |
setTimeout(() => resolve("Success!"), 1000); // 在1秒后resolve | |
}); | |
let result = await promise; // 等待Promise完成 | |
console.log(result); // 打印"Success!" | |
} |
在JavaScript中,宏任务(Macro-task)和微任务(Micro-task)是异步任务的两种分类,它们主要在事件循环中处理异步操作。
宏任务(Macro-task):在JavaScript中,宏任务主要包括:script(全体代码)、setTimeout、setInterval、setImmediate、I/O、UI渲染、MessageChannel、postMessage、XMLHttpRequest、EventSource、WebSocket、requestAnimationFrame等。每次宏任务都会清空微任务队列,并开始一个新的循环。
微任务(Micro-task):微任务通常比宏任务优先级更高,包括:Promise.then/catch/finally、process.nextTick、MutationObserver等。一个宏任务结束后,微任务队列中的所有任务都会被执行,直到队列清空。
JavaScript的事件循环(Event Loop)就是用来处理这两种任务的机制。事件循环的工作原理是:单线程执行栈中的代码(宏任务),当宏任务执行完成后,会检查微任务队列,如果有微任务,就执行所有的微任务,直到微任务队列清空。然后再次回到执行栈,执行下一个宏任务,如此循环。
这种机制确保了JavaScript的单线程特性,以及异步操作的正确执行。
在JavaScript中,异步任务主要可以分为以下几类:
- 回调函数(Callback Functions):这是JavaScript异步编程的最基础形式。在早期的JavaScript中,我们通常通过在函数参数中传递函数来实现异步操作。当异步操作完成时,回调函数被调用。但是,这种方式容易造成"回调地狱"(Callback Hell),使得代码难以理解和维护。
- Promise:Promise 是一个代表了异步操作最终完成或者失败的对象。它解决了回调地狱的问题,使得我们可以将异步操作串联起来,并且可以指定在异步操作完成或者失败时要执行的回调函数。
- Generator:Generator 是一个可以暂停和恢复的函数,它允许我们编写看起来像同步代码的异步代码。但是,Generator 的使用相对复杂,需要理解其工作原理和语法。
- async/await:async/await 是建立在Promise之上的新语法,使我们可以以同步的方式编写异步代码,极大地简化了异步操作。
- 事件循环(Event Loop)和消息队列(Message Queue):这是JavaScript处理异步任务的底层机制。所有的异步任务都会被放入消息队列中,然后在事件循环的每次迭代中处理。当一个异步任务完成时,它的回调函数会被添加到消息队列中等待执行。
- 观察者模式(Observer Pattern)和观察者数组(Array.observe()):这是一种在数据变化时自动执行回调的模式。然而,由于性能问题,观察者模式在JavaScript中已经不再推荐使用。
- Web Workers:Web Workers 是运行在后台的JavaScript线程,它们可以在主线程之外执行耗时的异步任务,避免阻塞主线程。
- Fetch API 和 Promise:Fetch API 是一个用于网络请求的新的原生API,它返回一个Promise对象,使得我们可以更方便地处理异步网络请求。
练习:
setTimeout(function () {
console.log('1');
});
// new Promise((resolve,reject)=>{})
new Promise(function (resolve){
console.log('2');
resolve();
}).then(function () {
console.log('3');
});
console.log('4');
async function async1() {
console.log(5);
const result = await async2();
console.log(6);
}
async function async2() {
console.log(7);
}
Promise.resolve().then(() => {
console.log(8);
});
setTimeout(() => {
console.log(9);
});
async1();
console.log(10);
// 主线程: 异步队列:读取文件 fs.readFile() 网络请求
// 打印2 微任务:打印3 宏任务:定时器打印1
// 打印4 打印8 定时打印9
// 打印5 打印6
// 打印7
// 打印10
//所以打印顺序是2、4、5、7、1、0、3、8、6、1、9