我正在使用流式multipart/form-data
解析器来处理文件和字段上传。每当解析一个新的字段或文件时,就会触发一个事件并执行一些处理。解析完所有字段和文件后,会触发'close'
事件,然后我会为请求调用终止函数。这是设置。
parser.on('fields',function(){
handleField(callback);
});
parser.on('close',function(){
dismiss();
})
问题是处理字段可能需要一些时间,这使得'close'侦听器在我有机会从字段处理侦听器调用回调之前解除请求。
我尝试使用setImmediate
函数,根据其描述
在p / I和计时器回调之后,将其回调排队到事件队列中。
为了让我的dismiss()
函数在所有当前计划的回调被执行后被调用但是没有用,我甚至尝试了process.nextTick()
以防我的订单被撤销,但是没有这样的运气。
所以问题是,只有在调用了处理函数的所有当前调度回调之后,我才能调用dismiss()
函数?
答案 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)
使用Promises和Promise.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()
接口尽早响应其结果是必要的。在您传入的函数中,您可以选择resolve
或reject
承诺。
使用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)
。