Javascript 我是否以正确的方式考虑回调?
我对node和js是如此的陌生,直到昨天我才知道回调和异步编程,所以对我说我是个白痴,因为我是 随着mixed.io的死,我想我会写我自己的小静态网站生成器。我看着狼吞虎咽,咕哝了一声,但还是选择了使用 构建css、缩小、列表等。。这非常简单,但当涉及到构建页面时,生活很快陷入了地狱 稍加阅读,我就有了页面构建脚本的开始:Javascript 我是否以正确的方式考虑回调?,javascript,node.js,callback,Javascript,Node.js,Callback,我对node和js是如此的陌生,直到昨天我才知道回调和异步编程,所以对我说我是个白痴,因为我是 随着mixed.io的死,我想我会写我自己的小静态网站生成器。我看着狼吞虎咽,咕哝了一声,但还是选择了使用 构建css、缩小、列表等。。这非常简单,但当涉及到构建页面时,生活很快陷入了地狱 稍加阅读,我就有了页面构建脚本的开始: var fm = require('front-matter'), fs = require('fs'), glob
var fm = require('front-matter'),
fs = require('fs'),
glob = require('glob'),
md = require('marked');
const SEARCHPATH = "content/pages/";
pages = [];
function searchFiles () {
glob("*.md", { cwd: SEARCHPATH }, readFiles);
}
function readFiles (err, files) {
if(err) throw err;
for (var file of files) {
fs.readFile(SEARCHPATH + file, 'utf8', processFiles);
}
}
function processFiles(err, data) {
if(err) throw err;
var attributes = fm(data).attributes;
var content = md(fm(data).body);
pages.push(attributes, content);
applyTemplate(pages);
}
function applyTemplate(pages) {
console.log(pages);
}
searchFiles();
但它看起来就像我即将坠入菊花链地狱,每个函数都会调用下一个函数,但如果不这样做,我就无法访问pages变量
这一切似乎都有点不对劲
我想的对吗?有什么更好的方法可以通过编程的方式来构造它
非常感谢Overflowers。您将所有回调分解为函数声明,而不是内联表达式,因此这已经是+1了,因为您有可以导出和测试的函数对象 对于这个响应,我假设优先级是独立的渐进式单元测试,而不模拟
require
。(当我进入一个新项目时,我通常会发现自己正在朝着这个方向重构legacy node.js)
当我沿着这种嵌套回调的路线走下去时,我认为最不容易使用的方法是将匿名表达式的嵌套链作为回调:(在psuedocode中)
以编程方式测试上述内容非常复杂。唯一的办法就是模仿别人。要测试readFile回调是否正常工作,您必须先控制所有调用!!!这违反了测试中的隔离
imo,第二个最好的方法是像您所做的那样中断回调
这允许更好地隔离单元测试,但仍然需要模拟fs
和glob
第三种最好的方法,imo,是注入所有函数依赖项,以允许轻松配置模拟对象。这看起来很奇怪,但对我来说,目标是100%覆盖,在独立的单元测试中,不使用模拟的require库。它使得每个函数都是一个独立的对象,易于测试,并为其配置模拟对象,但通常会使调用该函数更加复杂
为实现这一目标:
function searchFiles () {
glob("*.md", { cwd: SEARCHPATH }, readFiles);
}
将成为
function searchFiles (getFiles, getFilesCallback) {
getFiles("*.md", { cwd: SEARCHPATH }, getFilesCallback);
}
然后就可以用
searchFiles(glob, readFiles)
这看起来有点奇怪,因为它是一个单行函数,但说明了如何将依赖项注入到函数中,以便测试可以配置模拟对象并将它们直接传递给函数。重构readFiles
以执行此操作:
function readFiles (err, files, readFile, processFileCb) {
if(err) throw err;
for (var file of files) {
readFile(SEARCHPATH + file, 'utf8', processFileCb);
}
}
readFiles采用readFile
方法(fs.readFile
)并在读取文件后执行回调。这允许在编程测试中轻松配置模拟对象
然后,在psuedocode中,测试可以是:
it('throws err when error is found', function() {
var error = true;
assert throws readFiles(error)
});
it('calls readFile for every file in files', function() {
var files = ['file1'];
var error = false;
var readFile = createSpyMaybeSinon?();
var spyCallback = createSpy();
readFiles(error, files, readFile, spyCallback);
assert(readFile.calls.count(), files.length)
assert readFile called with searchpath + file1, 'utf8', spyCallback
});
一旦这些函数要求客户机提供所有函数依赖项,那么它们就需要一系列创造性的bind
ing回调,或者用小函数表达式包装调用
以上假设了一个最终目标,即在不需要模拟的情况下完成测试覆盖,这可能不是您的目标:)
imo的一种“更干净”的方法就是从一开始就使用承诺,这是对异步调用的极好的抽象。您将所有回调分解为函数声明,而不是内联表达式,因此这已经是+1,因为您有可以导出和测试的函数对象 对于这个响应,我假设优先级是独立的渐进式单元测试,而不模拟
require
。(当我进入一个新项目时,我通常会发现自己正在朝着这个方向重构legacy node.js)
当我沿着这种嵌套回调的路线走下去时,我认为最不容易使用的方法是将匿名表达式的嵌套链作为回调:(在psuedocode中)
以编程方式测试上述内容非常复杂。唯一的办法就是模仿别人。要测试readFile回调是否正常工作,您必须先控制所有调用!!!这违反了测试中的隔离
imo,第二个最好的方法是像您所做的那样中断回调
这允许更好地隔离单元测试,但仍然需要模拟fs
和glob
第三种最好的方法,imo,是注入所有函数依赖项,以允许轻松配置模拟对象。这看起来很奇怪,但对我来说,目标是100%覆盖,在独立的单元测试中,不使用模拟的require库。它使得每个函数都是一个独立的对象,易于测试,并为其配置模拟对象,但通常会使调用该函数更加复杂
为实现这一目标:
function searchFiles () {
glob("*.md", { cwd: SEARCHPATH }, readFiles);
}
将成为
function searchFiles (getFiles, getFilesCallback) {
getFiles("*.md", { cwd: SEARCHPATH }, getFilesCallback);
}
然后就可以用
searchFiles(glob, readFiles)
这看起来有点奇怪,因为它是一个单行函数,但说明了如何将依赖项注入到函数中,以便测试可以配置模拟对象并将它们直接传递给函数。重构readFiles
以执行此操作:
function readFiles (err, files, readFile, processFileCb) {
if(err) throw err;
for (var file of files) {
readFile(SEARCHPATH + file, 'utf8', processFileCb);
}
}
readFiles采用readFile
方法(fs.readFile
)并在读取文件后执行回调。这允许在编程测试中轻松配置模拟对象
然后,在psuedocode中,测试可以是:
it('throws err when error is found', function() {
var error = true;
assert throws readFiles(error)
});
it('calls readFile for every file in files', function() {
var files = ['file1'];
var error = false;
var readFile = createSpyMaybeSinon?();
var spyCallback = createSpy();
readFiles(error, files, readFile, spyCallback);
assert(readFile.calls.count(), files.length)
assert readFile called with searchpath + file1, 'utf8', spyCallback
});
一旦这些函数要求客户机提供所有函数依赖项,那么它们就需要一系列创造性的bind
ing回调,或者用小函数表达式包装调用
以上假设了一个最终目标,即在不需要模拟的情况下完成测试覆盖,这可能不是您的目标:)
imo的一种“更干净”的方法就是从一开始就使用承诺,这是对异步调用的一种极好的抽象。您在将st