JavaScript 异步流程控制的知识点整理

在写信息管理系统的时候,经常需要等待前面的任务执行完,比如最常见的Ajax请求后做弹窗提示,又或者是多个Ajax需要并行/串行执行操作等。

因为JS是单线程语言,所以在整个执行环境都依靠EventLoop。因此,实现异步除了最基本的Callback回调函数,社区还开发了Promise、Async/Await等语法糖或者库,我写一篇文章整理一下工作以来接触到的异步知识

Callback 回调函数

异步控制接触最早的,其实就是这个Callback回调函数,代码示例如下

// JQuery的Ajax请求
$.ajax({
   url: '/api/system/login',
   params: {},
   success: () => {
      console.log('在异步Ajax请求成功之后,会调用到此回调函数');
   }
});

// setTimeout/setInterval绑定回调函数
setTimeout(()=>{
    console.log('两秒之后会调用到此函数');
},2000)

// Node.js 经常的回调函数以及传参
var fs = require('fs');

fs.readFile('test.txt', 'utf-8', function(err, data) {
    // 这里同样绑定了回调函数,Node.js设计一般第一个参数是err,第二个参数是data数据等
    if(err) {
        console.error(err);
    } else {
	    console.log("readFile");
    }
});

如果信息系统交互逻辑变得复杂,这样的回调函数可能会不好维护,复杂的控制流程会导致回调函数层层叠加,从而造成了回调函数地狱(Callback Hell)。

比如说现在系统需要执行Ajax-1,然后再执行Ajax-2。按照回调函数的做法,是在Ajax-1的回调函数里,再发起一个Ajax-2,然后在Ajax-2的回调函数里再进行处理。

如果现在接口有变动,需要Ajax-1和Ajax-2调换一下顺序,或者是在Ajax-1和Ajax-2中间插入一个Ajax-3,又或者是需要Ajax-1和Ajax-2以及其他十几个请求一起串行去发送请求并依赖上一个请求结果,这个时候开发效率和代码维护可能就比较吃力了

使用async库来控制并行或者串行

因为写JQuery的事件绑定层层叠加,而业务系统的接口控制又需要比较复杂。在一番查询之后,我发现有一个开源第三方库——async

这个库提供了很多实用的异步方法,如series、 parallel、 waterfall等。

Promise

除了上面的库,还有Promise这个也是异步控制的一个办法,ES6标准提供了Promise这个对象。有了Promise对象,可以将层层重叠的回调函数转换一个个then方法,并且将值传入到下一个then方法的传参里。除此之外,还能通过catch方法捕获异步流程产生的错误

1,初始化Promise对象

let promise = new Promise((resolve,reject)=>{
   setTimeout(()=>{
      // 此处新增一个2000秒触发的异步事件,类似于Ajax
      // 如果一切正常,则调用resolve,如果产生错误或者异常,则调用reject
      resolve('两秒后触发的结果')
   },2000);
});

2,使用then或catch进行异步衔接

// 在promise后添加一个then方法,这个方法可以只传一个resolve参数
promise.then(resolveFunc);

// then方法也可以传两个参数,第一个表示resolve衔接,第二个表示reject衔接
promise.then(resolveFunc,rejectFunc);

// 上面的两个参数也可以改为then和catch的组合
promise.then(resolveFunc).catch(rejectFunc);

使用Co库和Generator实现同步逻辑编写

虽然有了Promise等then和catch来避免层层重叠,但是总体代码编写起来还是有一些不简明,尤其是有一些局部变量或者方法需要多个异步方法共享,then和catch写起来也会不太方便。于是我后面了解到有一个Tj写的Co库,能够很好的配合ES新特性Generator来编写类似Java同步的风格

Generator函数是ES6引进的新特性,它和普通函数的区别就是function旁边多了一个星号*。在这个函数里面,可以使用yield关键字,将当前处理的值返回给调用方,总体使用流程如下

function* func1(){
   // 我是生成器函数
   yield 1
   yield 2
   yield 3
}
let inst = func1();
console.log(inst.next()); // {value: 1, done: false}
console.log(inst.next()); // {value: 2, done: false}
console.log(inst.next()); // {value: 3, done: false}
console.log(inst.next()); // {value: undefined, done:true } 可以看到此处done为true,表示这个生成器实例已经全部生成完了

开源社区的TJ借助该特性,开发了Co库,然后编写代码可以变成类似Java同步的编写,并且能够yield一个数组或者对象,类似于async.series的方法,示例代码如下

co(function*(){
    // 在此处异步等待ajax,但是不用在嵌套或者加then
    let res_1 = yield axios({url: '/api/test/data'})
    // 不仅可以异步一个,然后异步等待多个(统一返回)
    let res_2 = yield [
       axios({url: '/api/test/1'}),
       axios({url: '/api/test/2'}),
    ]
    console.log(res_2); // 此处能拿到多个结果集
})

async/await

上面的Co和生成器方法,其实比较类似于async/await,目的都是为了编写同步风格的异步代码,我现在也主要用的是这个async/await,真的很方便!

async是修饰函数,await则是async函数里的关键字(类似于yield),用于等待异步返回结果,除了await,还可以用try/catch来对异步中产生的错误进行拦截,示例代码和说明如下

async function getData(){
    try{
      let res = await axios({url: '/api/system/test'})
      if(res.err == 1){
         throw new Error('产生错误');
      }else{
         return res.value
      }
    }catch(err){
       console.log('ajax结果异常',err);
    }
}

async function entry(){
    await getData();
}

// 触发entry方法
entry();

借助Promise在async/await里实现Sleep效果

在请求Ajax之前需要等待2秒,在async/await方法可以先定义一个sleep方法,然后await sleep(2000)即可,风格和Java很像,代码示例如下

function sleep(timeout){
   return new Promise((resolve,reject)=>{
      setTimeout(()=>{
         resolve(); // 此处2秒后才返回
      },timeout);
   })
}

async function getData(){
    await sleep(2000); // 睡眠2秒,然后继续操作
    await axios({url: '/api/test/data'}); // 2秒之后触发Ajax请求
}

文章总结

在企业信息系统里,尤其是ERP系统,涉及到比较复杂的前端逻辑控制,有了这些语法糖或第三方库能够让代码更加简洁,而且更方便维护。

其实之前写React,还有一个将异步做到更细腻的的库——Redux-Saga,提供了诸如TakeLastest、Fork、Cancel、Join等,但限于篇幅我再找时间另外写ReduxSaga的相关API,谢谢阅读!

发表评论

邮箱地址不会被公开。 必填项已用*标注