假设您需要执行一些依赖于某些临时文件的操作。以来 我们在这里谈论Node,这些操作显然是异步的。 什么是等待所有操作完成的惯用方法 知道什么时候可以删除临时文件?
以下是一些显示我想要做的代码:
do_something(tmp_file_name, function(err) {});
do_something_other(tmp_file_name, function(err) {});
fs.unlink(tmp_file_name);
但如果我这样写,第三次调用可以在前两次之前执行 有机会使用该文件。我需要一些方法来保证前两个 在没有嵌套的情况下继续调用之前已经完成调用(调用它们的回调) 调用(并使它们在实践中同步)。
我考虑过在回调上使用事件发射器并注册计数器 作为接收者。计数器将收到完成的事件并计算多少 运营仍在等待中。当最后一个完成时,它将删除 文件。但是存在竞争条件的风险,我不确定这是否存在 通常是这样做的。
Node人如何解决这类问题?
答案 0 :(得分:94)
<强>更新强>
现在我建议你看一下:
Promise对象用于延迟和异步计算。 承诺代表尚未完成的操作,但是 预计在未来。
流行的promises库是bluebird。 A建议您查看why promises。
你应该使用promises来解决这个问题:
fs.readFile("file.json", function (err, val) { if (err) { console.error("unable to read file"); } else { try { val = JSON.parse(val); console.log(val.success); } catch (e) { console.error("invalid json in file"); } } });
进入这个:
fs.readFileAsync("file.json").then(JSON.parse).then(function (val) { console.log(val.success); }) .catch(SyntaxError, function (e) { console.error("invalid json in file"); }) .catch(function (e) { console.error("unable to read file"); });
生成器:例如通过co。
基于生成器的nodejs和浏览器的控制流程良好性, 使用promises,让你以一种很好的方式编写非阻塞代码。
var co = require('co'); co(function *(){ // yield any promise var result = yield Promise.resolve(true); }).catch(onerror); co(function *(){ // resolve multiple promises in parallel var a = Promise.resolve(1); var b = Promise.resolve(2); var c = Promise.resolve(3); var res = yield [a, b, c]; console.log(res); // => [1, 2, 3] }).catch(onerror); // errors can be try/catched co(function *(){ try { yield Promise.reject(new Error('boom')); } catch (err) { console.error(err.message); // "boom" } }).catch(onerror); function onerror(err) { // log any uncaught errors // co will not throw any errors you do not handle!!! // HANDLE ALL YOUR ERRORS!!! console.error(err.stack); }
如果我理解正确,我认为你应该看看非常好的async库。你应该特别看看series。只是来自github页面的片段的副本:
async.series([
function(callback){
// do some stuff ...
callback(null, 'one');
},
function(callback){
// do some more stuff ...
callback(null, 'two');
},
],
// optional callback
function(err, results){
// results is now equal to ['one', 'two']
});
// an example using an object instead of an array
async.series({
one: function(callback){
setTimeout(function(){
callback(null, 1);
}, 200);
},
two: function(callback){
setTimeout(function(){
callback(null, 2);
}, 100);
},
},
function(err, results) {
// results is now equals to: {one: 1, two: 2}
});
此外,该库也可以在浏览器中运行。
答案 1 :(得分:22)
最简单的方法是在启动异步操作时递增整数计数器,然后在回调中递减计数器。根据复杂程度,回调可以将计数器检查为零,然后删除文件。
稍微复杂的是维护一个对象列表,每个对象都有你需要识别操作的任何属性(它甚至可以是函数调用)以及状态代码。回调会将状态代码设置为已完成。
然后你会有一个等待的循环(使用process.nextTick
)并检查是否所有任务都已完成。这种方法优于计数器的优点是,如果所有未完成的任务都可以完成,则在发出所有任务之前,计数器技术会导致您过早地删除文件。
答案 2 :(得分:11)
// simple countdown latch
function CDL(countdown, completion) {
this.signal = function() {
if(--countdown < 1) completion();
};
}
// usage
var latch = new CDL(10, function() {
console.log("latch.signal() was called 10 times.");
});
答案 3 :(得分:7)
没有“本机”解决方案,但节点有million flow control libraries。你可能会喜欢Step:
Step(
function(){
do_something(tmp_file_name, this.parallel());
do_something_else(tmp_file_name, this.parallel());
},
function(err) {
if (err) throw err;
fs.unlink(tmp_file_name);
}
)
或者,正如迈克尔所说,计数器可能是一个更简单的解决方案。看一下这个semaphore mock-up。你会这样使用它:
do_something1(file, queue('myqueue'));
do_something2(file, queue('myqueue'));
queue.done('myqueue', function(){
fs.unlink(file);
});
答案 4 :(得分:5)
我想提供另一种解决方案,它利用节点:事件核心的编程范式的速度和效率。
使用事件和简单的状态机可以完成使用Promises或用于管理流控制的模块(如async
)所做的一切,我相信这提供了一种方法,可能更容易理解比其他选择。
例如,假设您希望并行处理多个文件的长度:
const EventEmitter = require('events').EventEmitter;
// simple event-driven state machine
const sm = new EventEmitter();
// running state
let context={
tasks: 0, // number of total tasks
active: 0, // number of active tasks
results: [] // task results
};
const next = (result) => { // must be called when each task chain completes
if(result) { // preserve result of task chain
context.results.push(result);
}
// decrement the number of running tasks
context.active -= 1;
// when all tasks complete, trigger done state
if(!context.active) {
sm.emit('done');
}
};
// operational states
// start state - initializes context
sm.on('start', (paths) => {
const len=paths.length;
console.log(`start: beginning processing of ${len} paths`);
context.tasks = len; // total number of tasks
context.active = len; // number of active tasks
sm.emit('forEachPath', paths); // go to next state
});
// start processing of each path
sm.on('forEachPath', (paths)=>{
console.log(`forEachPath: starting ${paths.length} process chains`);
paths.forEach((path) => sm.emit('readPath', path));
});
// read contents from path
sm.on('readPath', (path) => {
console.log(` readPath: ${path}`);
fs.readFile(path,(err,buf) => {
if(err) {
sm.emit('error',err);
return;
}
sm.emit('processContent', buf.toString(), path);
});
});
// compute length of path contents
sm.on('processContent', (str, path) => {
console.log(` processContent: ${path}`);
next(str.length);
});
// when processing is complete
sm.on('done', () => {
const total = context.results.reduce((sum,n) => sum + n);
console.log(`The total of ${context.tasks} files is ${total}`);
});
// error state
sm.on('error', (err) => { throw err; });
// ======================================================
// start processing - ok, let's go
// ======================================================
sm.emit('start', ['file1','file2','file3','file4']);
将输出:
start: beginning processing of 4 paths forEachPath: starting 4 process chains readPath: file1 readPath: file2 processContent: file1 readPath: file3 processContent: file2 processContent: file3 readPath: file4 processContent: file4 The total of 4 files is 4021
请注意,流程链任务的顺序取决于系统负载。
您可以将程序流设想为:
start -> forEachPath -+-> readPath1 -> processContent1 -+-> done +-> readFile2 -> processContent2 -+ +-> readFile3 -> processContent3 -+ +-> readFile4 -> processContent4 -+
为了重复使用,创建一个支持各种流控制模式的模块是微不足道的,即串行,并行,批处理,直到等等。
答案 5 :(得分:2)
最简单的解决方案是按顺序运行do_something *和unlink,如下所示:
do_something(tmp_file_name, function(err) {
do_something_other(tmp_file_name, function(err) {
fs.unlink(tmp_file_name);
});
});
除非出于性能原因,你想并行执行do_something()和do_something_other(),否则我建议保持简单并按照这种方式执行。
答案 6 :(得分:1)
等待https://github.com/luciotato/waitfor
使用Wait.for:
var wait=require('wait.for');
...in a fiber...
wait.for(do_something,tmp_file_name);
wait.for(do_something_other,tmp_file_name);
fs.unlink(tmp_file_name);
答案 7 :(得分:0)
使用纯Promises可能会有些混乱,但是如果您使用Deferred Promises,那还不算太糟:
安装:
npm install --save @bitbar/deferred-promise
修改您的代码:
const DeferredPromise = require('@bitbar/deferred-promise');
const promises = [
new DeferredPromise(),
new DeferredPromise()
];
do_something(tmp_file_name, (err) => {
if (err) {
promises[0].reject(err);
} else {
promises[0].resolve();
}
});
do_something_other(tmp_file_name, (err) => {
if (err) {
promises[1].reject(err);
} else {
promises[1].resolve();
}
});
Promise.all(promises).then( () => {
fs.unlink(tmp_file_name);
});