在所有当前计划的callback被执行之后调用一个函数(节点)
我正在使用stream式multipart/form-data
parsing器来处理文件和字段上传。 每当一个新的字段或文件被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中,您可以selectresolve
或reject
承诺。
使用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(); } };