在执行所有当前计划的回调后调用函数(节点)

时间:2015-11-17 17:38:08

标签: javascript node.js events callback

我正在使用流式multipart/form-data解析器来处理文件和字段上传。每当解析一个新的字段或文件时,就会触发一个事件并执行一些处理。解析完所有字段和文件后,会触发'close'事件,然后我会为请求调用终止函数。这是设置。

parser.on('fields',function(){

    handleField(callback);

});


parser.on('close',function(){
    dismiss();
})

问题是处理字段可能需要一些时间,这使得'close'侦听器在我有机会从字段处理侦听器调用回调之前解除请求。

我尝试使用setImmediate函数,根据其描述

  在p / I和计时器回调之后,

将其回调排队到事件队列中。

为了让我的dismiss()函数在所有当前计划的回调被执行后被调用但是没有用,我甚至尝试了process.nextTick()以防我的订单被撤销,但是没有这样的运气。

所以问题是,只有在调用了处理函数的所有当前调度回调之后,我才能调用dismiss()函数?

2 个答案:

答案 0 :(得分:2)

一种可能性是使用一个库将NodeJS回调API调用转换为返回 promises 的调用。承诺很容易构成。有一些节点到承诺的库,例如promisify。 (我还没有使用它,不能保证,只是一个例子。)

但是,如果没有沿着这条路走下去,通常的答案就是让两个回叫都称为中心"完成"知道有多少呼叫未完成的方法:

var calls = 0;

++calls;
parser.on('fields',function(){

    handleField(callback);
    done();
});

++calls;
parser.on('close',function(){
    done();
})

function done() {
    if (--calls == 0) {
        dismiss();
    }
}

您甚至可以将其封装到实用程序对象中:

// ES2015 (ES6); ES5 translation below
class Tracker {
    constructor(callback) {
        this.count = 0;
        this.callback = callback;
    }

    start() {
        ++this.count;
    }

    stop() {
        if (--this.count) {
            this.callback();
        }
    }
}

然后

var track = new Tracker(function() {
    dismiss();
});

track.start();
parser.on('fields',function(){

    handleField(callback);
    track.stop();
});

track.start();
parser.on('close',function(){
    track.stop();
})

是的,它有点繁琐,这就是为什么承诺被发明的原因。 : - )

Tracker的ES5翻译:

function Tracker(callback) {
    this.count = 0;
    this.callback = callback;
}

Tracker.prototype.start = function() {
    ++this.count;
};

Tracker.prototype.stop = function() {
    if (--this.count) {
        this.callback();
    }
};

答案 1 :(得分:2)

使用PromisesPromise.all,您可以使dismiss以后的操作取决于通过回调的包装函数解析的所有Promise的解析。

诀窍是将回调包装到另一个函数中,该函数在异步(或同步)操作调用回调之后解析或拒绝包装器Promise。您可以将所有Promise存储在一个数组中,然后在该数组上调用Promise.all以产生另一个.then()的Promise。

在这个例子中,我假设callback是一个Node I / O风格的处理程序(即第一个参数是err)而handleField是一个异步节点最终调用callback的I / O样式操作。从语句"每当解析一个新的字段或文件时,就会触发一个事件并执行一些处理。" ,我也假设' ;字段'是一个N次出现的事件,其中N是字段数,因此必须先完成N个回调才能正确dismiss()。如果我的假设不正确,请随意发表评论。

var promises = [];

parser.on('fields', function() {
  promises.push(
    new Promise(function(resolve, reject) {

      // wrap the original callback into another function.
      // this function either resolves or rejects the Promise.
      handleField(function(err) {
        // if there's an error,
        // pass it to reject instead of throwing it.
        if (err) {
          reject(err);
        } else {
          // calls your callback with given args.
          // resolves Promise with return value of callback.
          resolve(callback.apply(null, arguments));
        }
      });

    })
  );
});


parser.on('close', function() {
  Promise.all(promises).then(function(values) {
    dismiss(); // All resolved.
  }, function(reason) {
    console.error(reason.stack); // First reject.
    process.exit(); // Exit on error.
  });
});

要学习Promises,您应该阅读Mozilla文档或其他一些着名的参考或教程,但我已经在下面添加了一些可以帮助您的关键点。

新承诺(fn)

使用构造函数创建新Promise时,传入的函数会立即调用 。这对于立即启动异步任务然后能够使用.then()接口尽早响应其结果是必要的。在您传入的函数中,您可以选择resolvereject承诺。

使用Promise.all

  

Promise.all(iterable)方法返回一个解析时间的promise   可迭代参数中的所有promise都已解决或拒绝   由于第一个通过的拒绝承诺的原因。

需要注意的重要事项是:

Promise.all([
  new Promise((resolve, reject) => setTimeout(() => resolve('work done'), 3000)),
  new Promise((resolve, reject) => reject('coffee depleted'))
]).then(
  (v) => console.log('all work done!'),
  (r) => console.error(`Error: ${r}`)
)

立即拒绝而不等待第一个Promise解析,因为第二个Promise提供了一个早期错误数组中的第一个Promise仍将最终解决,但重点是Promise.all早点拒绝,这很好。

<强>沉

如果您运行的是没有Promise的旧版Node,则可以安装{GntHub上提供的开源实现Promises library

npm install promise --save

然后只是require

var Promise = require('promise');

<强> ES6-promisify

你可以promisify异步函数handleFields,假设它是一个异步的Node I / O样式的操作,它调用带有err的回调作为它的第一个参数:

// npm install es6-promisify --save
var promisify = require("es6-promisify")

handleField = promisify(handleField);
promises.push(
  handleField().then(function(successArgs) {
    // success callback here
  }).catch(function(err) {
    console.error(err);
  })
);

总的来说,这看起来很干净。如果您正在使用Promise库,那么只需使用Promise.denodify(fn)