文档章节

Node.js Promise.all 限制并发数量

hell0cat
 hell0cat
发布于 2016/11/17 18:06
字数 765
阅读 541
收藏 1

Promise.all 本身不负责执行,执行过程在传递给Promise.all之前已经开始,Promise.all只等待全部执行完成,执行resolve,或碰到有执行失败,立即执行reject部分。Promise.all非常好用,唯一的问题是,不能限制并发数量,所有任务同时开始执行,因为Promise.all本身不负责执行具体任务,所以也无法实现并发控制。

实现一个简单的可以控制并发数量的Promise.allLimit函数,可以通过参数来控制并发数量。代码:

/* promise-limit.js */
/* jshint esversion: 6 */
/*jslint node: true */
Promise.allLimit = function(arr, wrap, limit, callback) {
  return new Promise((resolve, reject) => {
    var total = arr.length;
    var result = new Array(total);
    var rejected = false;
    var dones = 0;
    function run(n) {
      setTimeout(() => {
        wrap(n, arr.shift()).then(res => {
          return typeof callback === 'function' ? callback(n, res) : Promise.resolve(res);
        }).then(res => {
          dones++;
          result[n] = res;
          if (!rejected) {
            if (arr.length) {
              run(total - arr.length);
            } else if (dones === total) {
              resolve(result);
            }
          }
        }).catch(err => {
          rejected = true;
          reject(err);
        });
      }, 0);
    }
    arr.slice(0, limit).forEach((v, n) => {
      run(n);
    });
  });
};

同样返回一个Promise对象,可以直接替换Promise.all,不同的是,需要传递一个函数(wrap参数),用来包裹生成每一个具体执行的Promise对象,limit用来限定并发数量,在指定并发任务内,一个任务完成后,再吸入一个新任务继续执行。

callback用来解析每一次任务完成后所需要的后续动作,比如存储下载的内容、或将参数做变换,必须也返回一个Promise对象。

测试代码:

Promise.allLimit([2000, 1500, 2500, 3000, 1500], function(n, time) {
  return new Promise((resolve, reject) => {
    console.log("Start Job: ", n, time);
    // setTimeout(2500 === time ? reject : resolve, time, "Time: " + time); // 测试reject
    setTimeout(resolve, time, "Time: " + time);
  });
}, 2, (n, res) => {
  // log Job n done
  console.log("Done Job: ", res);
  return Promise.resolve(n);
// log Job n done
}).then(result => {
  console.log("All Done: ", result);
}).catch(err => {
  console.log("Error: ", err);
});

最后贡献一个下载妹子图的简单代码,默认控制并发数量10个。

#!/usr/bin/env node

/* jshint esversion: 6 */
/*jslint node: true */
require('./promise-limit.js');

const FS = require('fs');
const PATH = require('path');
const UTIL = require('util');
const ARGV = require('yargs').argv;
const REQUEST = require('request');
const CHEERIO = require('cheerio');

if (!ARGV.url || !ARGV.dir || !/\/$/.test(ARGV.dir) || !ARGV.img || !ARGV.total || !ARGV.from || !ARGV.to) {
  console.log("usage: --url http://example.com --dir ./imgs/ --img '.main-image img' --total '共(\d+)页' --from '.php' --to '_%d.php' ");
  process.exit();
}

const parallel = ARGV.parallel || 10; //并发数量

const headers = {
  'Referer': ARGV.url,
  'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
  'Accept-Encoding': 'gzip, deflate, sdch',
  'Accept-Language': 'en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4',
  'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.104 Safari/537.36'
};

function fetch(url, encoding = 'utf8') {
  return new Promise((resolve, reject) => {
    REQUEST({
      url: url,
      headers: headers,
      gzip: true,
      encoding: encoding
    }, (error, response, body) => {
      if (error) {
        reject(error);
      } else {
        resolve(body);
      }
    });
  });
}

function write(file, content) {
  return new Promise(function(resolve, reject) {
    FS.writeFile(file, content, function(err) {
      if (err) {
        reject(err);
      } else {
        resolve();
      }
    });
  });
}

fetch(ARGV.url)
  .then(rsp => {
    const total = parseInt((rsp.match(new RegExp(ARGV.total)) || [0, 0])[1]);
    if (!total) {
      throw new Error('Match total error');
    }
    console.log("Total: %d, Parallel: %d", total, parallel);
    return Promise.allLimit(Array.from({
      length: total
    }, (v, k) => k + 1), (k, v) => {
      return fetch(v === 1 ? ARGV.url : ARGV.url.replace(ARGV.from, UTIL.format(ARGV.to, v)));
    }, parallel, (k, res) => {
      let src = CHEERIO.load(res)(ARGV.img).attr('src');
      let file = (k + 1) + PATH.extname(src);
      return fetch(src, null).then(img => {
        return write(ARGV.dir + file, img);
      }).then(() => {
        console.log("OK: [" + file + "]\t" + src);
        return file;
      }).catch(err => {
        console.log("ER: [" + file + "]\t" + src + " : " + err.toString());
      });
    });
  })
  .then(rsp => {
    console.log("All Jobs Done: ");
    console.log(rsp);
  })
  .catch(err => {
    console.log("Fetch failed: %s", err.toString());
  });

执行:

 ./request.js --total '(\d+)</span></a><a[^<>]+><span>下一页' --from '/71636' --to '/71636/%d' --img '.main-image img' --dir ./71636/ --url 'http://www.mzitu.com/71636' --parallel 10

© 著作权归作者所有

共有 人打赏支持
hell0cat
粉丝 34
博文 47
码字总数 23694
作品 0
徐汇
程序员
编程学习之如何在Node.js中优化服务器端渲染?[图]

编程学习之如何在Node.js中优化服务器端渲染?[图] 在 Airbnb,我们花了数年时间将所有前端代码迁移到 React 架构,Ruby on Rails 在 Web 应用中所占的比例每天都在减少。实际上,我们很快会...

原创小博客
07/22
0
0
玩转异步 JS :async/await 简明教程(附视频下载)

课程介绍 在软件开发领域,简洁的代码 => 容易阅读的代码 => 容易维护的代码,而 ES2017 中的 async/await 特性能让我们编写出相比回调地狱和 Promise 链式调用更直观、更容易理解的代码,a...

王仕军
06/29
0
0
三大角度 PK ,Go 语言和 Node.js 谁胜谁负?

Node.js 与 Go 语言一直是互联网大战中的主战场,虽说按照普通的各项指标对比,那么这场战争可能在很长时间内都难分胜负,但我们还是决定尝试对这二者做一些研究,并力求做出更准确的判断。 ...

王练
07/01
0
46
三大角度PK,Go语言和Node.js谁胜谁负?

Node.js与Go语言一直是互联网大战中的主战场,虽说按照普通的各项指标对比,那么这场战争可能在很长时间内都难分胜负,但我们还是决定尝试对这二者做一些研究,并力求做出更准确的判断。 我们...

程序师
06/30
0
0
Node.js与Golang使用感受与小结【三】--JS异步流程控制(序列模式、并发模式、有限...

Node.js与Golang使用感受与小结 目录 一、互联网的基石TCP/IP协议 二、HTTP服务器编写与编程语言无关 三、构建HTTP服务器需要掌握的知识点 四、HTTP协议基础 五、Node.js简介 六、是前端选择...

念念之间
2013/06/13
0
1

没有更多内容

加载失败,请刷新页面

加载更多

下一页

kernel version does not match DSO version

错误信息: kernel version 384.11 does not match DSO version 384.130.0 原因是: cuda driver版本太低,不匹配DSO 简单有效的修复方法,升级nvidia driver, 步骤如下: 1. google seach ...

刘小米
今天
0
0
maven坐标和依赖

一、maven坐标详解 <groupId>com.fgt.club</groupId><artifactId>club-common-service-facade</artifactId><version>3.0.0</version><packaging>jar</packaging> maven的坐标元素说......

老韭菜
今天
1
0
springmvc-servlet.xml配置表功能解释

问:<?xml version="1.0" encoding="UTF-8" ?> 答: xml version="1.0"表示是此xml文件的版本是1.0 encoding="UTF-8"表示此文件的编码方式是UTF-8 问:<!DOCTYPE beans PUBLIC "-//SPRING//......

隐士族隐逸
今天
1
0
基于TP5的微信的公众号获取登录用户信息

之前讲过微信的公众号自动登录的菜单配置,这次记录一下在TP5项目中获取自动登录的用户信息并存到数据库的操作 基本的流程为:微信设置自动登录的菜单—>访问的URL指定的函数里获取用户信息—...

月夜中徘徊
今天
0
0
youTrack

package jetbrains.teamsys.license.runtime; 计算lis package jetbrains.ring.license.reader; 验证lis 安装后先不要生成lis,要把相关文件进行替换 ring-license-checker-1.0.41.jar char......

max佩恩
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部