在所有当前计划的callback被执行之后调用一个函数(节点)

我正在使用stream式multipart/form-dataparsing器来处理文件和字段上传。 每当一个新的字段或文件被parsing,一个事件被触发,我执行一些处理。 在所有的字段和文件被parsing后,一个'close'事件被触发,然后我调用请求的终止函数。 这是设置。

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

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

我试图使用根据其描述的setImmediate函数

在I / O和定时器callback之后将其callback排队在事件队列中。

为了让我的dismiss()函数在所有当前计划的callback被执行之后被调用,但是没有工作,我甚至尝试了process.nextTick()以防止我的顺序颠倒,但没有这样的运气。

所以问题是,只有在调用处理函数的所有当前计划的callback之后,我怎样才能调用我的dismiss()函数呢?

有了Promise和Promise.all ,你可以让你dismiss一个将来的操作,这个操作取决于所有Promise的parsing ,这个Promise通过你的callback的包装函数来parsing。

诀窍是将你的callback包装成另一个函数,在asynchronous(或同步)操作调用callback后,parsing或拒绝包装器Promise。 您可以将所有Promise存储在一个数组中,然后在该数组上调用Promise.all来产生另一个Promise to .then()

在这个例子中,我将假定callback是Node I / O风格的处理程序(即第一个参数是err ),而handleField是一个asynchronous的Node I / O风格的操作,最终调用callback 。 从语句“每当一个新的字段或文件被parsing,一个事件被触发,我执行一些处理”。 ,我也假设'fields'是N次出现的事件,N是N的字段数,因此N次callback必须在你正确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. }); }); 

要学习承诺,您应该阅读Mozilla文档或其他有信誉的参考或教程,但是我在下面添加了一些关键点,可以帮助您。


新承诺(fn)

当您使用构造函数创build新的Promise时,您传入的函数将立即被调用 。 这是立即启动asynchronous任务,然后能够使用.then()接口尽早响应其结果的必要条件。 在您通过的function中,您可以selectresolvereject承诺。


使用Promise.all

Promise.all(iterable)方法返回一个promise,当迭代参数中的所有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}`) ) 

立即拒绝而不等第一承诺解决,因为第二承诺提早出现错误arrays中的第一个承诺最终还是会解决的,但问题是Promise.all拒绝了,这很好。


如果您运行的是没有Promises的旧版本的节点,则可以安装Promise库 ,它是GitHub上提供的一个开源实现。

 npm install promise --save 

然后只require它:

 var Promise = require('promise'); 

ES6-promisify

假设它是一个asynchronous的节点I / O风格的操作,并以err作为第一个参数来调用callback函数,你可以提交asynchronous函数handleFields

 // 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)

一种可能性是使用一个将NodeJScallbackAPI调用转换为返回promise的调用的库。 诺言很容易编写。 那里有几个节点到承诺的库,比如promisify 。 (我没有使用它,不能保证,只是一个例子。)

但是,如果不走这条路,通常的答案是将两个callback调用称为中心的“完成”方法,该方法知道有多less呼叫是未完成的:

 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(); } };