Javascript 我是否以正确的方式考虑回调?

Javascript 我是否以正确的方式考虑回调?,javascript,node.js,callback,Javascript,Node.js,Callback,我对node和js是如此的陌生,直到昨天我才知道回调和异步编程,所以对我说我是个白痴,因为我是 随着mixed.io的死,我想我会写我自己的小静态网站生成器。我看着狼吞虎咽,咕哝了一声,但还是选择了使用 构建css、缩小、列表等。。这非常简单,但当涉及到构建页面时,生活很快陷入了地狱 稍加阅读,我就有了页面构建脚本的开始: var fm = require('front-matter'), fs = require('fs'), glob

我对node和js是如此的陌生,直到昨天我才知道回调和异步编程,所以对我说我是个白痴,因为我是

随着mixed.io的死,我想我会写我自己的小静态网站生成器。我看着狼吞虎咽,咕哝了一声,但还是选择了使用

构建css、缩小、列表等。。这非常简单,但当涉及到构建页面时,生活很快陷入了地狱

稍加阅读,我就有了页面构建脚本的开始:

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