1. 异步编程
摘录自" JavaScript 红宝书":理解什么是异步
同步行为和异步行为的对立统一是计算机科学的一个基本概念。特别是在 JavaScript 这种单线程事件循环模型中,同步操作与异步操作更是代码所要依赖的核心机制。异步行为是为了优化因计算量大而时间长的操作。如果在等待其他操作完成的同时,即使运行其他指令,系统也能保持稳定,那么这样做就是务实的。
重要的是,异步操作并不一定计算量大或要等很长时间。只要你不想为等待某个异步操作而阻塞线程执行,那么任何时候都可以使用。
2. 代码视角理解同步异步
同步行为对应内存中顺序执行的处理器指令;每条指令都会严格按照它们出现的顺序来执行。
2.1 同步代码
同步代码执行过程。若遇到alert、报错会卡住,不会往下执行同步代码
console.log('输出顺序1', 111)
alert(222) // alert会卡住,点击确认之后才会往下执行
console.log('输出顺序2', 333)
手动抛出错误,同步代码遇到报错,会卡住不再往下继续执行代码
console.log(111)
throw new Error()
console.log(222) // 不执行
2.2 异步代码
异步行为类似于系统中断,即当前进程外部的实体可以触发代码执行。异步操作经常是必要的,因为强制进程等待一个长时间的操作通常是不可行的(同步操作则必须要等)。如果代码要访问一些高延迟的资源,比如向远程服务器发送请求并等待响应,那么就会出现长时间的等待。
解决js单线程等待问题:同步代码执行时,若遇到异步代码会先跳过,继续执行完所有的同步代码,在回来按照异步栈的顺序来执行异步
从setTimeout理解异步
console.log('输出顺序1', 111)
setTimeout(() => {
alert(333) // alert不会卡住,输出完333才会执行这个alert
}, 3000)
console.log('输出顺序2', 222)
从Promise理解异步
const p = Promise.resolve(222)
p.then(res => {
console.log('输出顺序2', res)
})
console.log('输出顺序1', 111)
3. 哪些程序属于异步程序
- 浏览器级定义的API(宏任务): setTimeout、setInterval、网络请求(new XMLHttpRequest())
- ES6定义的API(微任务): Promise的then()/catch()/finally()、async/await(属于promise的语法糖)
- 相同类型的异步执行顺序和推入调用栈的先后顺序有关,先进入先执行;不同类型的异步执行顺序“微任务 > 宏任务”(为什么这样执行请看后续系列文章)
4. 异步回调
串联多个异步操作是一个常见的问题,通常需要深度嵌套的回调函数(俗称“回调地狱”)来解决。
传统的异步回调地狱问题:这种写法就很不优雅,且函数作为参数传递理解起来也比较困难
function fetchData(callback) {
setTimeout(() => {
console.log("Data fetched successfully."); // 1s后输出
callback();
}, 1000);
}
function processUserData(userData, callback) {
setTimeout(() => {
console.log(`Processing user data: ${userData}`); // 2s后输出
callback();
}, 1000);
}
function displayResult(result) {
console.log(`Result displayed: ${result}`); // 2s后输出
}
// 使用传统异步回调地狱
fetchData(() => {
processUserData("John", () => {
displayResult("Done");
});
});
使用Promise链式调用解决异步回掉地狱,并实现其功能:这种写法逻辑清晰,比较容易理解
function packagingPromise(val1 = '', val2 = null){
return new Promise((resolve) => {
setTimeout(() => {
resolve({val1, val2})
}, 1000);
})
}
packagingPromise("Data fetched successfully.")
.then(res => {
console.log(res.val1) // 1s后输出
return packagingPromise("Processing user data:", 'John')
})
.then(res => {
console.log(`${res.val1}${res.val2}`) // 2s后输出
return 'Done'
})
.then(res => {
console.log(res) // 2s后输出
})