Chciałbym zaoferować inne rozwiązanie, które wykorzystuje szybkość i wydajność paradygmatu programowania w samym rdzeniu Node: events.
Wszystko, co możesz zrobić z Obietnicami lub modułami zaprojektowanymi do zarządzania kontrolą przepływu, na przykład async, można osiągnąć za pomocą zdarzeń i prostego automatu stanu, który, jak sądzę, oferuje metodologię, która jest być może łatwiejsza do zrozumienia niż inne opcje.
Na przykład załóżmy, że chcesz zsumować długość wielu plików równolegle:
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']);
Który wyświetli:
start: rozpoczęcie przetwarzania 4 ścieżek
forEachPath: uruchomienie 4 łańcuchów procesów
readPath: plik1
readPath: plik2
processContent: plik1
readPath: plik3
processContent: plik2
processContent: plik3
readPath: plik4
processContent: plik4
Łącznie 4 pliki to 4021
Należy zauważyć, że kolejność zadań w łańcuchu procesów zależy od obciążenia systemu.
Możesz wyobrazić sobie przebieg programu jako:
start -> forEachPath - + -> readPath 1 -> processContent 1 - + -> done
+ -> readFile 2 -> processContent 2 - +
+ -> readFile 3 -> processContent 3 - +
+ -> readFile 4 -> processContent 4 - +
W przypadku ponownego wykorzystania byłoby trywialne utworzenie modułu obsługującego różne wzorce kontroli przepływu, tj. Szeregowe, równoległe, wsadowe, podczas, do itp.