概要: JavaScript是一门单线程语言,意味着同一时间内只能做一件事,但是这并不意味着单线程就会阻塞,而实现单线程非阻塞的方法就是事件循环。js这种单线程事件循环模型中,同步操作与异步操作是事件循环所要依赖的核心机制。
简介
异步编程 是为了优化因计算量大而时间长的操作。比如,张三上js基础网课,这件事中,因为上网课是实时同步的,而张三做练习是随时异步的,张三为了不打乱同步上网课的节奏,把异步做练习的操作,放在同步上完网课之后。这样一来,张三上js基础网课的事件顺序就是先同步上网课,后异步做练习。
图解
-
蓝框:所有的同步代码先放在代码调用栈中(call stack),按从上到下的循序前后执行;
-
红框:异步代码则全放在挂起区(macrotask宏任务:主要为DOM/BOM操作和API是脱离语法层面的、microtask微任务:主要为Promise/aysnc/await是es语法的范畴)
-
黄框:先把微任务放进事件循环(event loop),循环进入代码调用栈中调用,再DOM渲染,最后宏任务
微宏任务
注意:
-
如果回调时间相同,按先微任务、中DOM渲染、后宏任务的顺序调用执行
-
如果微任务中有两个Promise同时回调,那按代码先后循序,从上往下执行
-
若是调用Promise()或者async function(){}则先走里面的同步函数,遇到then、await就会挂起(因为堵塞后面的代码),直接往下执行,执行完后再按前后循序回调
习题
一、
二、
function eventLoop() {
async function async1() {
console.log('async1 start')//2
await 0//异步阻塞,挂起去代码顺序3
async2()//执行函数6
console.log('async1 end')//7
}
async function async2() {
console.log('async2')//6
}
// 代码顺序1
console.log('script start')//1
setTimeout(function () {
console.log('setTimeout')//9
}, 0)
// 代码顺序2
async1()//执行函数2
// 代码顺序3
new Promise(
function (resolve) {//执行函数3
console.log('promise1')//3
resolve()//履约往下
console.log('promise1.1')//4
}
)
.then(function () {//异步阻塞,挂起去代码顺序4
console.log('promise2')//8
})
// 代码顺序4,执行完后在函数体内从上往下回看"挂起"
console.log('script end')//5
}
异步编程常用于ajax网络请求、setTimeout定时函数、Promise(then/catch/finally)对象、async/await异步函数(es7的语法,是Promise的语法糖,不用再写then/catch/finally)
Promise
简介
以往的异步编程通常需要深度嵌套的回调函数,很容易产生“回调地狱”,这种情况理解起来很复杂,不利于代码维护。
// 1+2+3+4 + ... + 100
ajax({
url: "./mathserver.php",
data: { a: 1, b: 2 },
success(result) {
console.log(result);
ajax({
url: "./mathserver.php",
data: { a: result, b: 3 },
success(result) {
console.log(result);
ajax({
url: "./mathserver.php",
data: { a: result, b: 4 },
success(result) {
console.log(result);
ajax({
url: "./mathserver.php",
data: { a: result, b: 5 },
success(res){
console.log(res);
}
})
},
});
},
});
},
});
console.log("你妹");
因此,Promise承诺/契约/期约应运而生,一种异步编程的解决方案,是es6的新语法。
优点
-
链式操作降低了编码难度(Promise().then().catch().finally())
-
代码可读性提高
-
代码可维护性增强
状态
-
pending待定挂起态:初始状态
-
fulfilled兑现履约态:有时候也称为“resolved解决”,操作成功完成,HTTP响应状态码在200-299
-
rejected拒绝毁约态:操作失败
改变Promise的状态
控制期约状态的转换是通过调用resolve()履约和reject()毁约,这两个函数来实现。
写法
// es6 promise
Promise()
// 履约走then,value为履约的返回值
.then(value=>console.log(value))
// 捕捉回调链中任意环的报错/毁约,直接在这打印错误
.catch(error=>console.log(error))
// 不管执行过程是否报错,最后必执行
.finally(()=>console.log("最后必执行"))
// es7(async/await异步等待)
// async是Promise的语法糖,本质上是Promise,await是阻塞作用
async (function fn() {
try {
// await≈Promise.then()
let data = await ajaxPromise({url, data: {a: 1, b: 2}})
data = await ajaxPromise({url, data: {a: data.result, b: 3}})
data = await ajaxPromise({url, data: {a: data.result, b: 4}})
console.log("结果为", data);
}
// catch≈Promise.catch()
catch (error) {
console.log("error:", error);
}
// ≈Promise.finally()
console.log("over");
})()
console.log("continue");
// 拓展方法
// all():全荣共荣,一损俱损
Promise.all([promise1,promise2,promise3])
.then(value=>console.log(value))//全resolve才有
.catch(error=>console.log(error))//有一个reject就error
// allSettled():完全期约,无论resolve还是reject都要输出,明确每个promise的最终状态
// 不要用await测试,会报错,因为async/await挂起的特性
Promise.allSettled([promise1, promise2, promise3])
.then(values => values.forEach(item => console.log(item.status, item)))
// 完全期约结果如下
// fulfilled {status: 'fulfilled', value: 6}
// rejected {status: 'rejected', reason: '6.5'}
// fulfilled {status: 'fulfilled', value: 7}
// race()竞速契约,不管resolve还是reject,谁快,结果就是谁
Promise.race([promise1, promise2])
.then(value=>console.log(value))
.catch(error=>console.log(error))
参考文献
面试官系列
面试官:说说你对事件循环的理解 https://vue3js.cn/interview/JavaScript/event_loop.html
面试官:你是怎么理解ES6中 Promise的?使用场景 https://vue3js.cn/interview/es6/promise.html
《JavaScript高级程序设计(第4版 中文高清)》P321-326