Node学习十九 —— 回调地狱、Async库控制异步同步

23 篇文章 0 订阅

控制回调流程

当异步操作的时候,通常都是通过回调函数来进行反馈。

如果你需要进行一些IO操作,那么基本都需要指定一个在操作结束的时候调用的回调函数,如果涉及大量IO操作,要把这些回调管理好就很麻烦,也许会导致一些被称为“回调汤”或者“飞去来器效应”的模式。

一、飞去来器效应

这个效应发生在一组回调按顺序执行时——当一个IO操作结束,另一个操作开始。这个效应的名称源于,如果代码中回调过多,就会多层嵌套,导致代码长得像一个飞行器,也是我们所说的:回调地狱。

二、怎么避免飞去来器效应

可以通过给函数命令,并且在同一作用域声明它来避免飞去来器效应。

比如你需要执行A,B,C三个操作,那么你可以把ABC三个操作分别提出来,进行函数封装,在需要执行这一系列代码逻辑的时候调用入口函数A,等到A执行完毕之后,在A之中调用B,等B执行完毕,在B中调用C,这样就可以将每一步的逻辑写到同一个作用域下,从而不必陷入回调地狱,导致多层嵌套。

但是这样会有几个显著问题:

  • 每一步都需要处理错误,导致大量冗余代码
  • 每一步都需要知道下一步执行的函数名,耦合度高。

三、使用 ASYNC 流程控制库

async模块可以很方便简化异步模式:

可以用命令安装这个库:

npm install async

async模块有一组辅助函数,这些辅助函数可以进行一步迭代以及流程控制。

3.1 串行执行

使用async.series将两个异步操作串接起来:

const async = require('async')

function done(err, results){
    if(err) throw err;
    console.log(results);
}

async.series([
    function(next){
        异步操作
        next(err, ... );
    },
    function(next){
        异步操作
        next(err, ... );
    }
], done)

所有函数执行完毕之后,会调用done函数,此时done函数带有每个回调函数的异步结果。

3.2 并行执行

前面的例子中,每个回调函数逐个被执行,但是也可以让他们并行执行,此时只需要将series改成parallel即可。

3.3 连续传递

同样还可以执行一串函数,其中下一个函数的执行取决于前一个回调函数的结果:

function done(err, res, body){
    if(err) throw err;
    
    console.log(body);
}

async.waterfall([
    function(next){
        request.post({url:"...", body: "3"}, next);
    },
    function(res,body,next){
        request.post({url:"...", body: body}, next};
    }
], done)

每个回调函数都会得到最后一个操作(done)的回调参数(除了err),而如果出现错误,则会调用最后一个回调函数(done)。

3.4 排队

如果需要进行一些重复性的异步操作并想控制并发数——在给定时刻同时存在的正在进行的作业的最大数量——就可以使用saync.queue函数。

该函数创建一个队列,该队列基于一个函数处理其中的元素,队列的客户端可以将作业和并发作业的最大数量传入这个函数,并按顺序执行。

const maxSize = 5;

const work = function(task, callback){
    request.post(options, (err, res, body) => {
        callback(err, body && JSON.parse(body));
    })
}

const queue = saync.queue(work, maxSize);

[1,2,3,4,5,6,7,8,9,10].forEach(function(i) {
    queue.push(i, function(){
        ...
    })
})

上述代码构建了一个 work 函数,用这个函数来接收和处理任务。本例中的任务就是一个数字。

queue函数基于work函数创建了一个队列,并定义了最大并发数5。

使用队列的优点就是限制了并发量,可以用它来限制资源的流失。

可以通过给队列的 saturated 属性传入一个函数,来在队列并发达到上限的时候调用该函数:

queue.saturated = function(){
    console.log("full!");
}

同样,也可以给 empty 属性绑定函数,在队列中最后一项被分配给一个作业后调用。

当返回最后一项作业被返回,会调用 drain 属性上绑定的函数。

3.5 迭代

JS有一些函数可以用来在集合上面进行同步迭代,例如:

[1,2,3,4].forEach(function(i){
	...
})

如果不使用同步函数,就必须进行一些IO操作。

使用异步函数async.forEach()在对象集合上进行迭代:

function done(err){
    ...
}
    
const collection = [1,2,3,4];
    
function iterator(value , callback) {
    request.post({
        ...
    }, (err, res, body) => {
        ...
        callback();
    })
}
    
async.forEach(collection, iterator, done);

iterator 函数利用集合中的元素进行必要的IO操作,并且在IO操作结束之后会调用回调函数。当在所有元素上完成了异步迭代时,就会调用最后一个回调函数(done)。

async.forEach函数会针对每个元素并行调用迭代器,如果希望同步进行,可以使用async.forEachSeries函数。

3.6 映射

JS还提供了一个函数array.map(),用来同步迭代所有元素,不过迭代是异步进行的,只需要把forEach换成map即可,而且在获得所有结果之后可以按正确顺序返回给回调函数done。

3.7 规约

JS还有一个array.reduce函数,它将一个初始值和一个迭代器作为参数,并返回一个新值,然后将这个新值和新元素一起传入下一次迭代。

例如求和:

const collection = [1,2,3,4];
function iterator(sum, elem){
    return sum + elem;
}
const sum = collection.reduce(iterator,0);

async.reduce函数提供了相同的语义,但是他是异步进行的:

async.reduce(collection, 0, iterator, done);

3.8 过滤

可以基于一个过滤函数进行过滤:

const isEven(item){
    return item % 2 === 0;
}
const result =  collection.filter(isEven);

async.filter可以异步执行相似操作:

async.filter(collection, filter, done);

3.9 检测

如果你想当满足某个条件的时候停止迭代,可以使用detect

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沧州刺史

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值