使用promise创建分页执行任务

在实际应用中,我们总会遇到以下类似的情况:

  • 在构建爬虫时,需要对当前页面下的所有分页爬取结果页,并在结果页爬取相关数据.
  • 在发送结果数据时,需要按一个固定的队列数量,分次发送数据.
  • 在执行数据库操作时,需要固定数量插入.

这种分页型任务在很多应用中都需要自己实现.下面,本文将讨论具体的实现方法:

之前的文章中,我们使用promise的传递特性来实现中间件部分的功能.今天,我们将使用promise的特性用一个简单的函数来实现上文需要的分页任务方法.

实现思路

promise语法中,我们拥有Promise.all方法,通过Promise.all,我们可以让一串任务(数组)同时执行,并在所有任务执行完成后返回任务顺序相同的结果数组,如下所示:

let promiseList=[new Promise(function(resolve,reject){  
    //do something
},new Promise()(function(resolve,reject){
    //do something
})];
Promise.all(promiseList).then(function(result){  
    //Use result[0] & result[1] to get promise func result
}).catch(function(err){
    //Get error info
})

通过使用Promise.all,我们就可以使用如下方法来组合一个分页任务加以执行了.

函数方法

/**
 * 请求下个Promise
 * @param  {Object} err  错误信息
 * @param  {Object} data 传递数据
 * @return {Object}      Promise对象
 */
function nextPromise(err,data){  
    return new Promise(function(resolve,reject){
        if(err) reject(err);
        else resolve(data);
    });
}
/**
 * 创建Promise分页执行列表
 * @param  {Array}  promiseFuncList Promise函数列表
 * @param  {Number} pageSize        单页个数
 * @return {Object}                 Promise对象
 */
exports.buildPromiseListByPage = function(promiseFuncList,pageSize){  
    if(promiseFuncList.length===0) return nextPromise();
    let resultData=[];
    let roundCount=0;
    let totalRound=Math.ceil(promiseFuncList.length/pageSize);
    let promiseFunc=nextPromise();
    for(let i=0;i<totalRound;i++){
        promiseFunc=promiseFunc.then(function(){
            let promiseList=promiseFuncList.slice(i*pageSize,(i+1)*pageSize).map(function(curFunc){
                return curFunc();
            });
            return Promise.all(promiseList);
        });
    }
    return promiseFunc;
};

通过使用buildPromiseListByPage方法,我们可以快速创建一个分页发送任务.

如果你需要获取结果数据的话,就需要改造一下buildPromiseListByPage函数.把结果最终传递出去.

/**
 * 创建Promise分页执行列表
 * @param  {Array}  promiseList Promise对象列表
 * @param  {Number} pageSize    单页个数
 * @return {Object}             Promise对象
 */
exports.buildPromiseListByPage = function(promiseFuncList,pageSize){  
    if(promiseFuncList.length===0) return nextPromise();
    let resultData=[];
    let roundCount=0;
    let totalRound=Math.ceil(promiseFuncList.length/pageSize);
    let promiseFunc=nextPromise();
    for(let i=0;i<totalRound;i++){
        promiseFunc=promiseFunc.then(function(){
            let promiseList=promiseFuncList.slice(i*pageSize,(i+1)*pageSize).map(function(curFunc){
                return curFunc();
            });
            return Promise.all(promiseList)
            .then(function(result){
                resultData=resultData.concat(result);
            });
        });
    }
    return promiseFunc;
};

测试用例

最后,我们写个测试用例来测试是否和我们预期的一致.

为了确认是否按我们预期的轮次执行,我们加入了一个roundCount变量来做循环次数统计

func.js

'use strict';  
/**
 * 请求下个Promise
 * @param  {Object} err  错误信息
 * @param  {Object} data 传递数据
 * @return {Object}      Promise对象
 */
function nextPromise(err,data){  
    return new Promise(function(resolve,reject){
        if(err) reject(err);
        else resolve(data);
    });
}
exports.nextPromise=nextPromise;  
/**
 * 创建Promise分页执行列表
 * @param  {Array}  promiseFuncList Promise函数列表
 * @param  {Number} pageSize        单页个数
 * @return {Object}                 Promise对象
 */
exports.buildPromiseListByPage = function(promiseFuncList,pageSize){  
    if(promiseFuncList.length===0) return nextPromise();
    let resultData=[];
    let roundCount=0;
    let totalRound=Math.ceil(promiseFuncList.length/pageSize);
    let promiseFunc=nextPromise();
    for(let i=0;i<totalRound;i++){
        promiseFunc=promiseFunc.then(function(){
            let promiseList=promiseFuncList.slice(i*pageSize,(i+1)*pageSize).map(function(curFunc){
                return curFunc();
            });
            return Promise.all(promiseList)
            .then(function(result){
                roundCount++;
                resultData=resultData.concat(result);
            });
        });
    }
    return promiseFunc.then(function(){
        return nextPromise(null,[roundCount].concat(resultData));
    });
};

test.js

下面使用mocha来写一个测试用例,文件放在./test/test.js.

'use strict';  
const func = require('../func.js');  
describe('function', function(){  
    it('Call buildPromiseListByPage', function (done) {
        let promiseList=[];
        let testString='test string';
        let testObject={'test':'test'};
        let testArray=[1];
        let testObjectArray=[testObject];
        promiseList.push(function(){
            return func.nextPromise(null,1);
        });
        promiseList.push(function(){
            return func.nextPromise(null,testString);
        });
        promiseList.push(function(){
            return func.nextPromise(null,testObject);
        });
        promiseList.push(function(){
            return func.nextPromise(null,testArray);
        });
        promiseList.push(function(){
            return func.nextPromise(null,testObjectArray);
        });
        func.buildPromiseListByPage(promiseList,3)
        .then(function(results){
            let validResult=results.every(function(result,index){
                switch(index){
                    case 0:
                        return result===2;
                    case 1:
                        return result===1;
                    case 2:
                        return result===testString;
                    case 3:
                        return result===testObject;
                    case 4:
                        return result===testArray;
                    case 5:
                        return result===testObjectArray;
                }
            });
            if(!validResult) throw new Error('不符合预期的测试结果');
            done();
        }).catch(function(err){
            done(err);
        })
    });
    it('Call buildPromiseListByPage with empty', function (done) {
        let promiseList=[];
        func.buildPromiseListByPage(promiseList,3)
        .then(function(result){
            if(result){
                done(new Error('不符合预期的测试结果'));
            }else{
                done();
            }
        })
        .catch(function(err){
            done(err);
        })
    });
    it('Call buildPromiseListByPage with error', function (done) {
        let promiseList=[];
        promiseList.push(function(){
            return func.nextPromise(new Error('测试错误'));
        });
        func.buildPromiseListByPage(promiseList,3)
        .then(function(){
            done(new Error('不符合预期的测试结果'));
        })
        .catch(function(err){
            if(err.message==='测试错误') done();
            else done(err);
        })
    });
});