节点中并发活动('map-reduce')的惯用同步?
Idiomatic sync of concurrent activities ('map-reduce') in node?
编辑
多亏了答案,我有了一个可用的版本。问题末尾的代码;感谢@estus 和@Jared 的帮助。
原题
我正在学习 Node,并尝试掌握并发性。从一个简单的例子开始:给定两个文件的名称,确定哪个更大。常规(顺序)解决方案:
var fs = require('fs');
var fname1=process.argv[2]
var fname2=process.argv[3]
var stats1 = fs.statSync(fname1)
size1=stats1["size"]
var stats2 = fs.statSync(fname2)
size2=stats2["size"]
if(size1 > size2) {
console.log(fname1 + " is bigger")
} else if (size2 > size1) {
console.log(fname2 + " is bigger")
} else {
console.log("The files are the same size")
}
现在假设我想并行统计文件*。我可以将代码转换为使用异步 stat
函数:
var fs = require('fs');
var fname1=process.argv[2]
var fname2=process.argv[3]
fs.stat(fname1, function doneReading(err, stats) {
size1=stats["size"]
fs.stat(fname2, function doneReading(err, stats) {
size2=stats["size"]
if(size1 > size2) {
console.log(fname1 + " is bigger")
} else if (size2 > size1) {
console.log(fname2 + " is bigger")
} else {
console.log("The files are the same size")
}
})
})
但是:
- 可读性较差;
- 如果我想比较 >2 个文件,它将无法很好地扩展;
- 不确定它是否会并行统计文件(我不清楚 atm 后台线程是如何工作的)。
所以,具体来说,惯用的方法是什么:
- 同时产生多个动作,然后
- 全部完成后使用它们的综合结果?
也许promises可能是一个候选人? Promise.all
看起来像是等待所有承诺的方式,但不清楚如何实际使用它们的结果。
谢谢。
解决方案
'use strict';
const co = require('co');
const fs = require('fs-promise');
var fname1=process.argv[2]
var fname2=process.argv[3]
co(function* () {
let res = yield [fs.stat(fname1), fs.stat(fname2)];
let size1 = res[0]["size"]
let size2 = res[1]["size"]
if(size1 > size2) {
console.log(fname1 + " is bigger")
} else if (size2 > size1) {
console.log(fname2 + " is bigger")
} else {
console.log("The files are the same size")
}
})
它非常易读、简洁,并且完全没有回调脏话。并且易于扩展以比较 n 文件。
--
*是的,我知道在这种情况下没有必要这样做;目的是通过一个简单的例子来理解模式。
fs.stat(fname1, function doneReading(err, stats) {
...
fs.stat(fname2, function doneReading(err, stats) {
...
还是顺序的,不是并行的,和fs.statSync
的区别是fs.stat
是非阻塞的。
现代 Node 中建议的 'readable' 方法是 promises 和 co
。 fs.stat
可能被 promisified(使用 pify
或 Bluebird 的 Promise.promisify
/Promise.promisifyAll
)。或者可以使用一些现有的 promisified fs
包,比如 fs-promise
.
上述代码的顺序和非阻塞替代方案可能如下所示:
'use strict';
const co = require('co');
const fs = require('fs-promise');
co(function* () {
let stat1 = yield fs.stat(fname1);
let stat2 = yield fs.stat(fname2);
...
});
如果我们想让它并行,Promise.all
步骤:
co(function* () {
let [stat1, stat2] = yield [fs.stat(fname1), fs.stat(fname2)];
// a shortcut for
// let [stat1, stat2] = yield Promise.all([fs.stat(fname1), fs.stat(fname2)]);
...
});
除了estus的优秀回答,这可能更容易理解:
let stat = Promise.promisify(fs.stat.bind(fs));
Promise.all(arrOfPaths.map(stat)).then(arrOfResults => {
// do stuff
});
如前所述,您需要编写 promisify 函数或使用添加它的库。
这是一个示例实现:
const promisify = fn => {
return function(...args) {
return new Promise((resolve, reject) => {
fn.apply(this, [...args, (err, ...rest) => {
if (err) {
reject(err);
}
let result;
switch (rest.length) {
case 0:
result = true;
break;
case 1:
result = rest[0];
break;
default:
result = rest;
break;
}
resolve(result);
}]);
});
};
};
编辑
多亏了答案,我有了一个可用的版本。问题末尾的代码;感谢@estus 和@Jared 的帮助。
原题
我正在学习 Node,并尝试掌握并发性。从一个简单的例子开始:给定两个文件的名称,确定哪个更大。常规(顺序)解决方案:
var fs = require('fs');
var fname1=process.argv[2]
var fname2=process.argv[3]
var stats1 = fs.statSync(fname1)
size1=stats1["size"]
var stats2 = fs.statSync(fname2)
size2=stats2["size"]
if(size1 > size2) {
console.log(fname1 + " is bigger")
} else if (size2 > size1) {
console.log(fname2 + " is bigger")
} else {
console.log("The files are the same size")
}
现在假设我想并行统计文件*。我可以将代码转换为使用异步 stat
函数:
var fs = require('fs');
var fname1=process.argv[2]
var fname2=process.argv[3]
fs.stat(fname1, function doneReading(err, stats) {
size1=stats["size"]
fs.stat(fname2, function doneReading(err, stats) {
size2=stats["size"]
if(size1 > size2) {
console.log(fname1 + " is bigger")
} else if (size2 > size1) {
console.log(fname2 + " is bigger")
} else {
console.log("The files are the same size")
}
})
})
但是:
- 可读性较差;
- 如果我想比较 >2 个文件,它将无法很好地扩展;
- 不确定它是否会并行统计文件(我不清楚 atm 后台线程是如何工作的)。
所以,具体来说,惯用的方法是什么:
- 同时产生多个动作,然后
- 全部完成后使用它们的综合结果?
也许promises可能是一个候选人? Promise.all
看起来像是等待所有承诺的方式,但不清楚如何实际使用它们的结果。
谢谢。
解决方案
'use strict';
const co = require('co');
const fs = require('fs-promise');
var fname1=process.argv[2]
var fname2=process.argv[3]
co(function* () {
let res = yield [fs.stat(fname1), fs.stat(fname2)];
let size1 = res[0]["size"]
let size2 = res[1]["size"]
if(size1 > size2) {
console.log(fname1 + " is bigger")
} else if (size2 > size1) {
console.log(fname2 + " is bigger")
} else {
console.log("The files are the same size")
}
})
它非常易读、简洁,并且完全没有回调脏话。并且易于扩展以比较 n 文件。
--
*是的,我知道在这种情况下没有必要这样做;目的是通过一个简单的例子来理解模式。
fs.stat(fname1, function doneReading(err, stats) {
...
fs.stat(fname2, function doneReading(err, stats) {
...
还是顺序的,不是并行的,和fs.statSync
的区别是fs.stat
是非阻塞的。
现代 Node 中建议的 'readable' 方法是 promises 和 co
。 fs.stat
可能被 promisified(使用 pify
或 Bluebird 的 Promise.promisify
/Promise.promisifyAll
)。或者可以使用一些现有的 promisified fs
包,比如 fs-promise
.
上述代码的顺序和非阻塞替代方案可能如下所示:
'use strict';
const co = require('co');
const fs = require('fs-promise');
co(function* () {
let stat1 = yield fs.stat(fname1);
let stat2 = yield fs.stat(fname2);
...
});
如果我们想让它并行,Promise.all
步骤:
co(function* () {
let [stat1, stat2] = yield [fs.stat(fname1), fs.stat(fname2)];
// a shortcut for
// let [stat1, stat2] = yield Promise.all([fs.stat(fname1), fs.stat(fname2)]);
...
});
除了estus的优秀回答,这可能更容易理解:
let stat = Promise.promisify(fs.stat.bind(fs));
Promise.all(arrOfPaths.map(stat)).then(arrOfResults => {
// do stuff
});
如前所述,您需要编写 promisify 函数或使用添加它的库。
这是一个示例实现:
const promisify = fn => {
return function(...args) {
return new Promise((resolve, reject) => {
fn.apply(this, [...args, (err, ...rest) => {
if (err) {
reject(err);
}
let result;
switch (rest.length) {
case 0:
result = true;
break;
case 1:
result = rest[0];
break;
default:
result = rest;
break;
}
resolve(result);
}]);
});
};
};