我是否以正確的方式考慮回調? (Am I thinking about Callbacks the right way?)


問題描述

我是否以正確的方式考慮回調? (Am I thinking about Callbacks the right way?)

我對 node 和 js 還很陌生,直到昨天我才知道回調和異步編程,所以跟我說話就像我是個白痴一樣,因為我是......

有mix.io 之死我想我會編寫自己的小型靜態站點構建器。我看著 gulp 和 grunt 但對使用 npm 作為構建工具

構建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();

但它像我一樣尋找全世界 m 即將陷入菊花鏈地獄,每個函數都調用下一個函數,但如果不這樣做,我就無法訪問 pages 變量。

這一切似乎有點不對勁。

我在想這個嗎?以編程方式構建此結構的更好方法是什麼?

一如既往地感謝Overflowers。


參考解法

方法 1:

You broke out all of the callbacks into function declarations instead of inline expressions so that is already +1, because you have function objects that can be exported and tested.

For this response I'm assuming the priority is isolated progrmatic unittests, without mocking require. (Which I usually find myself refactoring legacy node.js towards, when I enter a new project).

When I go down this route of nested nested callbacks I think the least easy way to work with is a nested chain of anonymous expressions as callbacks: (in psuedocode)

function doFiles() {
   glob('something', function(files) {
      for (f in files) {
         fs.readFile(file, function(err, data) {
            processFile(data);
         }

      }
   }
}

Testing the above programmatically is pretty convoluted. The only way to do it is to mock requires. To test that readFile callback is working, you have to control all calls before it!!! Which violates isolation in tests.

The 2nd best approach, imo, is to break out callbacks as you have done.

Which allows better isolation for unittests, but still requires mocking requires for fs and glob.

The 3rd best approach, imo, is injecting all a functions dependencies, to allow easy configuration of mock objects. This often looks very weird, but for me the goal is 100% coverage, in isolated unittests without using a mock require library. It makes it so each function is an isolated object that is easy to test, and configure mock objects for, but often makes calling that function more convoluted!

To achieve this:

function searchFiles () {
    glob("*.md", { cwd: SEARCHPATH }, readFiles);
}

Would become

function searchFiles (getFiles, getFilesCallback) {
    getFiles("*.md", { cwd: SEARCHPATH }, getFilesCallback);
}

Then it could be called with

searchFiles(glob, readFiles)

This looks a little funky because it is a one line function, but illustrates how to inject the dependencies into your functions, so that your tests can configure mock objects and pass them directly to the function. Refactoring readFiles to do this:

function readFiles (err, files, readFile, processFileCb) {
    if(err) throw err;

    for (var file of files) {
        readFile(SEARCHPATH + file, 'utf8', processFileCb);
    }
}

readFiles takes in a readFile method (fs.readFile and callback to execute once the file is read. Which allows easy configuration of mock objects in programmatic testing.

Then tests could be, in 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
});

Once you have these functions that require the client to provide all of the functions dependencies, then they require a dance of creative binding of callbacks, or small functional expressions to wrap calls.

The above assumes an endgoal of complete test coverage without mocking requires, which might not be your goal :)


A "cleaner" approach imo is just to use promises from the beginnning, which is a wonderful abstraction over asynchronous calls.

(by Adamdm03514)

參考文件

  1. Am I thinking about Callbacks the right way? (CC BY‑SA 2.5/3.0/4.0)

#javascript #Callback #node.js






相關問題

為什麼我不能在 IE8 (javascript) 上擴展 localStorage? (Why can't I extend localStorage on IE8 (javascript)?)

在 Javascript 中打開外部 sqlite3 數據庫 (Open external sqlite3 database in Javascript)

Javascript:數組中的所有對像都具有相同的屬性 (Javascript: All Objects in Array Have Same Properties)

為什麼我們要在 javascripts 原型中添加函數? (Why do we add functions to javascripts prototype?)

顯示 URL javascript 的最後一部分? (Display the last part of URL javascript?)

Javascript XMLHttpRequest:忽略無效的 SSL 證書 (Javascript XMLHttpRequest: Ignore invalid SSL Certificate)

有沒有辦法測試 console.log 整體 (Is there a way to test for console.log entires)

如何從 javascript 對像中獲取名稱和值到新列表中? (How to get name and values from a javascript object into a new list?)

數據未發布..幫助!html,js,firebase (Data not posting.. Help! html,js,firebase)

使用 Node.js 腳本查看表單數據 (Seeing form data with Node.js script)

使用百分比查找範圍內的值 (find the value within a range using percent)

如何通過 react.js 中的組件傳遞變量或數據? (How to pass varible or data through components in react.js?)







留言討論