【问题标题】:Q pattern for many queued operations许多排队操作的 Q 模式
【发布时间】:2014-03-23 05:03:06
【问题描述】:

我正在为 node.js 编写一个小脚本,以从文件中读取一堆图像名称(2.5k),调整图像大小并将它们输出到目录。我天真的方式导致文件句柄用完:

//get the list of images, one per line in the file
var imgs = file.split('\n');
//keep track of how many images we've processed
var done = imgs.length;
var deferred = Q.defer();

for (var i = 0; i < imgs.length; i++) {
  (function resizeImg(img) {
    //open the file for writing the resized image to
    var stream = fs.createWriteStream('images/' + img);

    stream
      .on('open', function () {
        //now that it's opened, resize the source image, and write it
        //out to the stream
        gm(img)
          .resize(200, 200)
          .write(stream, function (err) {
            //we're finished writing - if there was an error, reject
            //otherwise, we can resolve the promise if this was the last image
            if (err)
              deferred.reject(err);
            else if (--done <= 0)
              deferred.resolve();
          });
      });
  })(imgs[i]);
}

return deferred.promise;

我真正需要做的是将所有调整大小操作排队并按顺序运行它们,这样它就不会同时打开所有文件,但我不知道该怎么做。这类事情有标准模式吗?

【问题讨论】:

  • 您可以同时打开多少个文件?你能动态检测(并且你想适应)这个吗?还是一次只运行一个?

标签: javascript node.js promise q


【解决方案1】:

你可以这样做吗:

//get the list of images, one per line in the file
var imgs = file.split('\n');
//keep track of how many images we've processed
var done = imgs.length;

//Store an array of functions to be executed in sequence
var funcArr = [];

for (var i = 0; i < imgs.length; i++) {
  //push a promise function onto the array
  funcArr.push((function resizeImg(img) {
    return function () {
        var deferred = Q.defer();

        //open the file for writing the resized image to
        var stream = fs.createWriteStream('images/' + img);

        stream
          .on('open', function () {
            //now that it's opened, resize the source image, and write it
            //out to the stream
            gm(img)
              .resize(200, 200)
              .write(stream, function (err) {
                //we're finished writing - if there was an error, reject
                //otherwise, we can resolve the promise if this was the last image
                if (err)
                  deferred.reject(err);
                else
                  deferred.resolve();
              });
          });

          return deferred.promise;
    }
  })(imgs[i]));
}

//Sequences as described at http://documentup.com/kriskowal/q/
var result = Q();
funcArr.forEach(function (f) {
    result = result.then(f, function (reason) {
        //Default error handler for each image in the sequence that does a reject
    });
});

//At this point result is a promise that will be resolved when all images have processed
return result;

for 循环的每次迭代都会将一个返回 promise 的函数推送到 funcArr 数组中。在 for 循环之后使用 Q 将 Promise 链接在一起以进行排序。这应该确保在移动到下一个图像之前处理一个图像。

【讨论】:

  • 谢谢!我或多或少地采用了这种方式,只是以一种更功能化的编程方式,就像一个实验一样。以您链接到的 Q github 自述文件为例,我将 resize 函数映射到图像数组上,然后将其缩小如下:file.split('\n').map(resizeImg).reduce(Q.when, Q())
【解决方案2】:

虽然不是必需的,但将调整大小的过程分离成一个返回承诺的函数更容易。

这使您可以在主程序中看到树木的木材。

function resizeImages(file) {

    //An inner utility function that returns a function that does the hard work and, importantly, that returns promise.
    function resize(img) {
        return function() {
            var deferred = Q.defer(),
                stream = fs.createWriteStream('images/' + img);
            stream.on('open', function() {
                gm(img).resize(200, 200).write(stream, deferred.resolve);//Always resolve, even if error is reported, thus allowing the .then chain to continue.
            });
            return deferred.promise;
        }
    }

    var p = Q();//resolved starter promise

    //main routine - build a .then chain
    for(var imgs=file.split("\n"), i=0; i<imgs.length; i++) {
        p = p.then(resize(imgs[i])).then(function(err) {
            //Yup, we're handling reported errors in the success handler!
            if(err) {
                //Handle error here.
                //throw(something) to stop the process or don't throw(anything) to continue.
            }
        });
    };
    return p;
}

【讨论】:

  • 在成功处理程序中处理错误看起来像是一种反模式。更好地正确构建承诺。
  • 到底什么是“反模式”Bergi?你可以有更好或更坏,但反......!关键是只有那些以err 形式报告给.on('open') 回调的错误才会以这种方式处理。当然,您可以将此类错误路由到失败路径,但是在回调中而不是在 .then() 成功处理程序中这样做没有特定的逻辑。除了避免反模式(doh!)之外,您只会增加代码没有特别的好处。我更喜欢deferred.resolve 的简洁性。
  • 反模式、代码异味、不良做法等等——即使我使用了错误的术语,它也绝对不干净。如果您之前真的想通过成功处理程序路由错误,那应该保留在resize 函数中。为简洁起见,请使用.write(stream, deferred.makeNodeResolver())
  • 感谢您的回答。我同意为了清晰起见,通常最好将函数分开,并且只是为了问题简洁而将其内联。不过,我担心我会倾向于同意@Bergi 在错误处理方面的观点,自从帕特里克第一次到达那里以来,我已经给了他答案。
猜你喜欢
  • 2023-04-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-27
  • 1970-01-01
  • 2013-03-30
  • 1970-01-01
相关资源
最近更新 更多