JavaScript中的异步方式
首先我们聊下JavaScript的异步处理的几种方式
- Callback - 回调函数
- Events - 事件监听
- Pub/Sub - 订阅发布
- Promise
本文不去细聊这些东西的API和使用,有需要的同学可以参考MDN或者阮一峰大佬的ES6系列书
Callback
回调函数是在JavaScript代码中比较常见的形式,比如jQuery时代,我们去写获取接口内容的代码
$.ajax({url:"/api/cars/list",success:function(result){
$("#div1").html(result);
}});
很多小伙伴,包括我身边的一些同行,一提到Callback的时候就直接喷,说怎么怎么辣鸡。其实Callback本身没什么问题,问题就在于如果出现嵌套了,也就是我们常说的回调地狱。
$.ajax({url:"/api/cars/list",success:function(result){
$("#div1").html(result);
result.map(result,function(item){
$.ajax({url:`/api/car/${item.id}`,success:function(result){
$("#div2").html(result);
....
});
}});
}});
当然上面就是段伪代码,从jQuery的ajax模块功能上看请求既有成功,又会可能失败,也有可能超时,一旦出现嵌套,那么这个代码就写的及其冗长而又复杂。因为事实上的业务代码,如果功能设计不太合适,开发者自身认知问题,四五层嵌套的也不少见。
Promise
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
这个是ES6入门的一个例子,比较简单,就是实现个延时功能,100ms后,执行then中的函数,value就是"done"。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
因此比较常见的Promise代码结构是这样的。then中处理成功场景,catch中处理异常场景,finally处理不论成功还是异常,都要执行的代码。
那我们看看,promise如何解决回调地狱问题
fetch('/api/getUserinfo')
.then((user)=>{
return fetch(`/api/weather/${user.location}`)
}).then((weather)=>{
...
});
我们可以看到,在promise的加持下,我们可以将回调地狱改成链式调用的方式,使得整体代码的可读性和可维护性得到加强。强调下,demo中fetch的用法不代表其真实使用,也不包含异常处理等,仅仅为了释义。
貌似promise的引入好多了,可以链式调用也不是特别爽啊,就没有类似同步代码似的写法么?
async await
async function xxx(){
const user=await fetch('/api/getUserinfo');
const weather=await fetch(`/api/weather/${user.location}`);
...
}
哇,终于舒服了。
其实在async方案之前,还有Generator方案,在一些框架和库的实现中我们还能看到,比如dva的model层中的effect。这里重点强调下,有些会有人说async是promise的语法糖,这个是错误的,async是Generator的语法糖,对比下
- Generator 出现在ES2015中,async 出现在ES2017中,async 是 Generator 的语法糖;
- 执行方式不同,Generator 执行需要使用执行器(next()等方法);async 函数自带执行器,与普通函数的执行一样;
- async 的语法语义更加清楚,async 表示异步,await 表示等待;而 Generator 函数的(*)号和 yield 的语义就没那么直接了;
- Generator 中 yield 后面只能跟 Thunk 函数或 Promise 对象;而 async 函数中 await 后面可以是 promise 对象或者原始类型的值(会自动转为立即resovle的promise对象);
- 返回值不同,Generator 返回遍历器,相比于 async 返回 promise 对象操作更加麻烦。
当然还包括异常处理等,有兴趣的同学可以去仔细挖一挖。
什么样的叫滥用
知乎的博主编程爱好者的一篇文章有张图,我们引用下。大家也可以去看下原文【传送门】
当在回调地狱受苦受难的前端开发,看到async方案的时候,绝对跟看到程序员鼓励师一样,结果乱手一摸就出事了。
从这个例子的对话上看出来,不论是不是任务间存在先后顺序,都在一个等一个去执行,这样是浪费效率的。
根据实际情况我们可以分组,再使用promise.all配合
async function 上厕所(){
await 解带()
await 开闸();
await 收口();
}
async function 加油(){
await 开盖();
await 拔枪();
await 加油();
...
}
async function 去加油站(){
const 加油Promise=加油();
const 上厕所Promise=上厕所();
await 加油Promise;
await 上厕所Promise;
...
}
// 还有种方式就是Promise.all
async function 去加油站(){
Promise.all([加油(),上厕所()]).then(()=>{
...
});
}
比较随意的一个例子,可以帮助大家理解下。上厕所和加油这两件事本质上是可以并行处理的,没必要上完厕所再去加油,或者加完油再上厕所。
喜欢的话,大家可以点个关注。想跟笔者深入交流的话,可以搜下公粽号:喵爸的小作坊