JS语言的执行环境是“单线程”,而这有可能导致任务卡死的问题。为了解决该问题,JS将任务的执行模式分成两种:同步和异步。本文主要介绍4种异步编程方案解决回调问题。当然“事件监听”和“观察者模式”也属于js异步编程,但不在本文介绍。
JS的4种常见异步编程解决方案
1、回调函数 (es6之前)
2、new Promise (es6引入)
3、generator/yiled函数 (es6引入)
4、async/await (es8引入)
本文默认你已了解相关语法知识,既然说实现对比,我们举个例子:
案例:执行第一个步骤,等待执行完成将结果给第二个步骤;执行第二个步骤,再等待完成将结果给第三个步骤;等待执行第三个步骤完成最后输出。
1、回调函数callback实现
function step1(value1,callback) {
setTimeout(()=>{
let result = value1+"-step1";
console.log(result);
callback(result);
},1000)
}
function step2(value2,callback) {
setTimeout(()=>{
let result = value2+"-step2";
console.log(result);
callback(result);
},1000)
}
function step3(value3,callback) {
setTimeout(()=>{
let result = value3+"-step3";
console.log(result);
callback(result);
},1000)
}
step1("start",function(value1){
step2(value1,function(value2){
step3(value2,function(value3){
console.log("回调函数结束:",value3);
})
})
})
//1s后打印start-step1,2s后打印start-step1-step2,3s后打印start-step1-step2-step3
//3s后同时打印:回调函数结束start-step1-step2-step3结论:嵌套多个回调函数,多层回调的致命缺点就是形成“回调地狱”,不利于代码的阅读和理解。各部分高度耦合,别说看别人多层回调代码,看自己的都得绕晕。
2、new Promise (es6引入)
function step1(value1){
return new Promise(resolve=>{ //注意promise是同步执行,then才是异步执行。
setTimeout(()=>{
let result =value1+"-step1";
console.log(result);
resolve(result);
},1000)
})
}
function step2(value2){
return new Promise(resolve=>{
setTimeout(()=>{
let result =value2+"-step2";
console.log(result);
resolve(result);
},1000)
})
}
function step3(value3){
return new Promise(resolve=>{
setTimeout(()=>{
let result =value3+"-step3";
console.log(result);
resolve(result);
},1000)
})
}
let result = step1("start")
.then(value1=>{
return step2(value1); //记得return
})
.then(value2=>{
return step3(value2);
})
.then(value3=>{
console.log("promise结束",value3);
});
//1s后打印start-step1,2s后打印start-step1-step2,3s后打印start-step1-step2-step3
//3s后同时打印:promise结束 start-step1-step2-step3结论:promise实现多层回调存在问题是需要处理多个then链,代码也不简洁。而且无法取消 Promise,错误需要通过回调函数捕获。
3、generator/yiled函数 (es6引入)
//省略函数step1,step2,step3同上的promise一致
function* runningTask() {
try {
var value1 = yield step1("start");
var value2 = yield step2(value1);
var value3 = yield step3(value2);
console.log("generator/yield结束",value1,value2,value3);
} catch (e) {
}
}
let task=runningTask();
let mystep1=task.next();
mystep1.value.then((value1)=>{
let mystep2=task.next(value1);
mystep2.value.then((value2)=>{
let mystep3=task.next(value2);
mystep3.value.then((value3)=>{
task.next(value3);
})
})
})
//1s后打印start-step1,2s后打印start-step1-step2,3s后打印start-step1-step2-step3
//3s后同时打印:generator/yield结束 start-step1 start-step1-step2 start-step1-step2-step3结论:generator最大的特点就是可以控制函数的执行。但手动迭代generator函数很麻烦,实现逻辑也比较绕。
4、async/await (es8引入)
//省略函数step1,step2,step3同上的promise一致
async function mystep(){
let mystep1 = await step1("start");
let mystep2 =await step2(mystep1);
let mystep3 =await step3(mystep2);
console.log("async/await结束",mystep1,mystep2,mystep3);
}
mystep();
//1s后打印start-step1,2s后打印start-step1-step2,3s后打印start-step1-step2-step3
//3s后同时打印:async/await结束 start-step1 start-step1-step2 start-step1-step2-step3结论:用同步的写法执行异步的操作。async函数实际是 Generator 函数的语法糖,是对Generator的改进。写法简洁清晰易读,优雅解决“回调地狱”和“多then链”的问题。是目前js异步编程的终极解决方案。
结论/建议
(1) 当只有一次回调时,可以使用回调函数,也可以用promise,然后用.then来获取值。
(2) 当有多次回调时,推荐使用async/await异步函数。避免回调函数的“回调地狱”和promise的多then链处理。可以说async/await是目前js异步编程的终极解决方案。
后记
感谢您的阅读,不知你是否对四种异步编程方案实现差异有一定的理解?第一次在掘金写文章,平时都是整理在自己的有道云笔记,后边希望分享更多文章与大家一起学习讨论,如果觉得本文不错请点个赞,若对本文有任何的意见或建议,欢迎在评论中一起探讨。