2010-11-20 51 views
145

我想創建一個顯示數據庫中某些數據的頁面,所以我創建了一些從我的數據庫中獲取數據的函數。我只是在Node.js的新手,所以據我瞭解,如果我想用所有的人都在一個單一頁面(HTTP響應)我不得不窩他們都:如何避免Node.js中異步函數的長嵌套

http.createServer(function (req, res) { 
    res.writeHead(200, {'Content-Type': 'text/html'}); 
    var html = "<h1>Demo page</h1>"; 
    getSomeDate(client, function(someData) { 
    html += "<p>"+ someData +"</p>"; 
    getSomeOtherDate(client, function(someOtherData) { 
     html += "<p>"+ someOtherData +"</p>"; 
     getMoreData(client, function(moreData) { 
     html += "<p>"+ moreData +"</p>"; 
     res.write(html); 
     res.end(); 
     }); 
    }); 
    }); 

如果有這樣的功能,那麼嵌套成爲問題

有沒有辦法避免這種情況?我想這與你如何組合多個異步函數有關,這似乎是一些基本的東西。

+12

所以,當你有10個異步功能,你有10個級別的縮進? – 2010-11-20 20:32:56

+0

此鏈接可能會有所幫助。 http://stackoverflow.com/a/4631909/290340 – 2012-01-24 22:28:47

+0

另一個問題:在'getSomeDate'和'getSomeOtherDate'之間插入另一個函數最終會改變許多行的縮進,這使得git歷史難以閱讀('git blame'甚至在此之後無用),並且在手動執行此操作時可能會產生錯誤 – 2014-12-30 02:57:45

回答

69

有趣的觀察。請注意,在JavaScript中,通常可以用命名函數變量替換內聯匿名回調函數。

以下:

http.createServer(function (req, res) { 
    // inline callback function ... 

    getSomeData(client, function (someData) { 
     // another inline callback function ... 

     getMoreData(client, function(moreData) { 
     // one more inline callback function ... 
     }); 
    }); 

    // etc ... 
}); 

可以改寫成這個樣子:

var moreDataParser = function (moreData) { 
    // date parsing logic 
}; 

var someDataParser = function (someData) { 
    // some data parsing logic 

    getMoreData(client, moreDataParser); 
}; 

var createServerCallback = function (req, res) { 
    // create server logic 

    getSomeData(client, someDataParser); 

    // etc ... 
}; 

http.createServer(createServerCallback); 

但是,除非你打算重用在其他地方回調的邏輯,它往往更容易閱讀內聯匿名函數,如你的例子。它也將免除您必須爲所有回調找到名稱。

另外請注意,由於@pst在下面的註釋中註明,如果您正在訪問內部函數內的閉包變量,上述內容將不是一個簡單的翻譯。在這種情況下,使用內聯匿名函數更爲可取。

+24

但是,(並且實際上僅僅是爲了理解折衷)在非嵌套時,變量*上的某些閉包語義可能會丟失,因此它不是直接翻譯。在上面的例子中,getMoreData中'res'的訪問會丟失。 – 2010-11-20 20:08:17

+0

@pst:是的好點。讓我在我的答案中澄清一下。 – 2010-11-20 20:10:54

+2

我認爲你的解決方案已經壞了:'someDataParser'確實解析了所有數據,因爲它也調用'getMoreData'。從這個意義上講,函數名稱是不正確的,很明顯我們並沒有真正移除嵌套問題。 – 2013-10-02 15:49:13

11

你需要的是一些語法糖。赤了這一點:

http.createServer(function (req, res) { 
    res.writeHead(200, {'Content-Type': 'text/html'}); 
    var html = ["<h1>Demo page</h1>"]; 
    var pushHTML = html.push.bind(html); 

    Queue.push(getSomeData.partial(client, pushHTML)); 
    Queue.push(getSomeOtherData.partial(client, pushHTML)); 
    Queue.push(getMoreData.partial(client, pushHTML)); 
    Queue.push(function() { 
    res.write(html.join('')); 
    res.end(); 
    }); 
    Queue.execute(); 
}); 

漂亮整齊,不是嗎?您可能會注意到,html成爲一個數組。這部分是因爲字符串是不可變的,所以你最好在數組中緩衝輸出,而不是放棄更大和更大的字符串。另一個原因是因爲bind的另一個很好的語法。

的例子Queue實際上只是一個例子,與partial一起可以如下

// Functional programming for the rescue 
Function.prototype.partial = function() { 
    var fun = this, 
     preArgs = Array.prototype.slice.call(arguments); 
    return function() { 
    fun.apply(null, preArgs.concat.apply(preArgs, arguments)); 
    }; 
}; 

Queue = []; 
Queue.execute = function() { 
    if (Queue.length) { 
    Queue.shift()(Queue.execute); 
    } 
}; 
+1

Queue.execute()將一個接一個地執行部分,而不等待異步調用的結果。 – ngn 2010-11-22 05:30:25

+0

現貨,謝謝。我已經更新了答案。這裏是一個測試:http://jsbin.com/ebobo5/edit(帶有可選的'last'函數) – galambalazs 2010-11-22 20:36:42

+0

嗨galambalazs和謝謝你的答案!在我的情況下,每個縮進都可以訪問內聯閉包變量。因此,例如,函數的工作方式如下所示:獲取HTTP req/res,從數據庫獲取用於cookie的用戶標識,爲稍後的用戶標識獲取電子郵件,爲以後的電子郵件獲取更多數據,...,爲以後的Y獲取X ...如果我沒有弄錯,那麼你建議的代碼只能確保異步函數按照正確的順序執行,但是在每個函數體中都沒有辦法獲得由我原始代碼中的閉包自然提供的變量。是這樣嗎? – 2010-12-05 20:01:41

3

你做了什麼有采取非同步模式,它適用於3個函數調用序列,每個實施一個在開始前等待前一個完成 - 也就是說你已經使它們同步。關於異步編程的一點是,您可以同時運行多個函數,而無需等待每個函數完成。如果getSomeDate()沒有提供任何getSomeOtherDate(),它沒有提供任何getMoreData(),那麼你爲什麼不像js允許的那樣異步地調用它們,或者它們是相互依賴的(而不是異步的)把它們寫成一個單一的函數?

你並不需要使用嵌套控制流量 - 例如,瞭解各種函數調用,用於確定何時所有3個已完成,然後發送響應的共同作用來完成。

18

大多數情況下,我會同意Daniel Vassallo。如果你可以將一個複雜的深層嵌套函數分解成單獨的命名函數,那麼這通常是一個好主意。對於在單個函數中執行它的意義時,您可以使用許多node.js異步庫中的一個。人們已經想出了很多不同的方法來解決這個問題,所以看看node.js模塊頁面,看看你的想法。

我已經爲此編寫了一個模塊,名爲async.js。利用這一點,上面的例子可以被更新爲:對這種做法

http.createServer(function (req, res) { 
    res.writeHead(200, {'Content-Type': 'text/html'}); 
    async.series({ 
    someData: async.apply(getSomeDate, client), 
    someOtherData: async.apply(getSomeOtherDate, client), 
    moreData: async.apply(getMoreData, client) 
    }, 
    function (err, results) { 
    var html = "<h1>Demo page</h1>"; 
    html += "<p>" + results.someData + "</p>"; 
    html += "<p>" + results.someOtherData + "</p>"; 
    html += "<p>" + results.moreData + "</p>"; 
    res.write(html); 
    res.end(); 
    }); 
}); 

的一個好處是,你可以快速改變你的代碼通過改變「系列」功能「水貨」平行獲取數據。更重要的是,async.js將 也可以在瀏覽器中運行,所以如果遇到任何棘手的異步代碼,您可以使用與node.js中相同的方法。

希望有用!

+0

Hi Caolan,謝謝你的回答!在我的情況下,每個縮進都可以訪問內聯閉包變量。因此,例如,函數的工作方式如下所示:獲取HTTP req/res,從數據庫獲取用於cookie的用戶標識,爲稍後的用戶標識獲取電子郵件,爲以後的電子郵件獲取更多數據,...,爲以後的Y獲取X ...如果我沒有弄錯,那麼你建議的代碼只能確保異步函數按照正確的順序執行,但是在每個函數體中都沒有辦法獲得由我原始代碼中的閉包自然提供的變量。是這樣嗎? – 2010-12-05 20:03:50

+3

您試圖實現的是從體系結構上稱爲數據管道。對於這種情況你可以使用異步瀑布。 – 2012-06-27 09:06:13

2

假設你可以這樣做:

http.createServer(function (req, res) { 
    res.writeHead(200, {'Content-Type': 'text/html'}); 
    var html = "<h1>Demo page</h1>"; 
    chain([ 
     function (next) { 
      getSomeDate(client, next); 
     }, 
     function (next, someData) { 
      html += "<p>"+ someData +"</p>"; 
      getSomeOtherDate(client, next); 
     }, 
     function (next, someOtherData) { 
      html += "<p>"+ someOtherData +"</p>"; 
      getMoreData(client, next); 
     }, 
     function (next, moreData) { 
      html += "<p>"+ moreData +"</p>"; 
      res.write(html); 
      res.end(); 
     } 
    ]); 
}); 

您只需要執行鏈(),使其部分每個功能適用於下一個,並立即調用只有第一個功能:

function chain(fs) { 
    var f = function() {}; 
    for (var i = fs.length - 1; i >= 0; i--) { 
     f = fs[i].partial(f); 
    } 
    f(); 
} 
+0

您好,感謝您的回答!在我的情況下,每個縮進都可以訪問內聯閉包變量。因此,例如,函數的工作方式如下所示:獲取HTTP req/res,從數據庫獲取用於cookie的用戶標識,爲稍後的用戶標識獲取電子郵件,爲以後的電子郵件獲取更多數據,...,爲以後的Y獲取X ...如果我沒有弄錯,那麼你建議的代碼只能確保異步函數按照正確的順序執行,但是在每個函數體中都沒有辦法獲得由我原始代碼中的閉包自然提供的變量。是這樣嗎? – 2010-12-05 20:02:04

62

凱,只需使用這些模塊之一。

它會變成這樣:

dbGet('userIdOf:bobvance', function(userId) { 
    dbSet('user:' + userId + ':email', '[email protected]', function() { 
     dbSet('user:' + userId + ':firstName', 'Bob', function() { 
      dbSet('user:' + userId + ':lastName', 'Vance', function() { 
       okWeAreDone(); 
      }); 
     }); 
    }); 
}); 

進入這個:

flow.exec(
    function() { 
     dbGet('userIdOf:bobvance', this); 

    },function(userId) { 
     dbSet('user:' + userId + ':email', '[email protected]', this.MULTI()); 
     dbSet('user:' + userId + ':firstName', 'Bob', this.MULTI()); 
     dbSet('user:' + userId + ':lastName', 'Vance', this.MULTI()); 

    },function() { 
     okWeAreDone() 
    } 
); 
+9

快速瀏覽一下flow-js,step和async,看起來他們只處理函數執行的順序。在我的情況下,每個縮進都可以訪問內聯閉包變量。因此,例如,函數的工作方式如下所示:獲取HTTP req/res,從數據庫獲取用於cookie的用戶標識,爲稍後的用戶標識獲取電子郵件,爲以後的電子郵件獲取更多數據,...,爲以後的Y獲取X ...如果我沒有弄錯,這些框架只能確保異步函數按照正確的順序執行,但是在每個函數體中都沒有辦法獲得由閉包自動提供的變量(?)Thanks :) – 2010-12-05 20:07:05

+8

對這些圖書館進行排名,我在Github上查看了每個「星級」的數量。異步擁有最多約3000個,步驟接下來約有1000個,其他則明顯更少。當然,他們並不都做同樣的事情:-) – kgilpin 2012-08-09 20:19:16

+3

@KayPale我傾向於使用async.waterfall,並且有時會爲每個階段/步驟分配我自己的函數,這些函數將傳遞下一步需要的內容或定義async.METHOD調用之前的變量,以便下線可用。也將使用METHODNAME.bind(...)進行我的異步。*調用,這也可以很好地工作。 – Tracker1 2013-01-04 23:26:38

0

我有同樣的問題。我已經看到了節點運行異步函數的主要庫,並且它們呈現非自然鏈(您需要使用三個或更多方法confs等)來構建您的代碼。

我花了幾個星期的時間來開發一個簡單易讀的解決方案。請嘗試EnqJS。所有的意見將不勝感激。

相反的:

http.createServer(function (req, res) { 
    res.writeHead(200, {'Content-Type': 'text/html'}); 
    var html = "<h1>Demo page</h1>"; 
    getSomeDate(client, function(someData) { 
    html += "<p>"+ someData +"</p>"; 
    getSomeOtherDate(client, function(someOtherData) { 
     html += "<p>"+ someOtherData +"</p>"; 
     getMoreData(client, function(moreData) { 
     html += "<p>"+ moreData +"</p>"; 
     res.write(html); 
     res.end(); 
     }); 
    }); 
    }); 

與EnqJS:

http.createServer(function (req, res) { 
    res.writeHead(200, {'Content-Type': 'text/html'}); 
    var html = "<h1>Demo page</h1>"; 

    enq(function(){ 
    var self=this; 
    getSomeDate(client, function(someData){ 
     html += "<p>"+ someData +"</p>"; 
     self.return(); 
    }) 
    })(function(){ 
    var self=this; 
    getSomeOtherDate(client, function(someOtherData){ 
     html += "<p>"+ someOtherData +"</p>"; 
     self.return(); 
    }) 
    })(function(){ 
    var self=this; 
    getMoreData(client, function(moreData) { 
     html += "<p>"+ moreData +"</p>"; 
     self.return(); 
     res.write(html); 
     res.end(); 
    }); 
    }); 
}); 

觀察該代碼似乎比以前更大。但它不像以前一樣嵌套。 使其顯得更自然,鏈被稱爲imediately:

enq(fn1)(fn2)(fn3)(fn4)(fn4)(...) 

並說,這回,我們調用函數中:我做一個相當原始而有效的方法

this.return(response) 
0

。例如。我需要得到其父母和子女的典範,讓我們說我需要爲他們做單獨的查詢:

var getWithParents = function(id, next) { 
    var getChildren = function(model, next) { 
     /*... code ... */ 
     return next.pop()(model, next); 
     }, 
     getParents = function(model, next) { 
     /*... code ... */ 
     return next.pop()(model, next); 
     } 
     getModel = function(id, next) { 
     /*... code ... */ 
     if (model) { 
      // return next callbacl 
      return next.pop()(model, next); 
     } else { 
      // return last callback 
      return next.shift()(null, next); 
     } 
     } 

    return getModel(id, [getParents, getChildren, next]); 
} 
7

戀愛了Async.js自從我發現了它。它有一個async.series函數可以用來避免長時間嵌套。

文檔: -


系列(任務,[回調])

運行的功能串聯的陣列,每一個運行一次先前的功能已完成。 [...]

參數

tasks - 的函數數組的運行,每個函數傳遞迴調它必須在完成呼叫。 callback(err, [results]) - 一個可選的回調函數,在所有函數完成後運行。該函數獲取傳遞給數組中使用的回調的所有參數的數組。


下面是我們如何能夠將其應用到您的示例代碼: -

http.createServer(function (req, res) { 

    res.writeHead(200, {'Content-Type': 'text/html'}); 

    var html = "<h1>Demo page</h1>"; 

    async.series([ 
     function (callback) { 
      getSomeData(client, function (someData) { 
       html += "<p>"+ someData +"</p>"; 

       callback(); 
      }); 
     }, 

     function (callback) { 
      getSomeOtherData(client, function (someOtherData) { 
       html += "<p>"+ someOtherData +"</p>"; 

       callback(); 
      }); 
     }, 

     funciton (callback) { 
      getMoreData(client, function (moreData) { 
       html += "<p>"+ moreData +"</p>"; 

       callback(); 
      }); 
     } 
    ], function() { 
     res.write(html); 
     res.end(); 
    }); 
}); 
0

使用纖維https://github.com/laverdet/node-fibers它使異步代碼看起來像同步(不阻塞)

我個人使用的小包裝http://alexeypetrushin.github.com/synchronize 我的項目中的代碼示例(每種方法實際上是異步的,使用異步文件IO)我甚至害怕想象使用回調或異步控制流助手會發生什麼混亂庫。

_update: (version, changesBasePath, changes, oldSite) -> 
    @log 'updating...' 
    @_updateIndex version, changes 
    @_updateFiles version, changesBasePath, changes 
    @_updateFilesIndexes version, changes 
    configChanged = @_updateConfig version, changes 
    @_updateModules version, changes, oldSite, configChanged 
    @_saveIndex version 
    @log "updated to #{version} version" 
17

你可以用數組而不是嵌套函數或模塊來使用這個技巧。

在眼睛上更容易。

var fs = require("fs"); 
var chain = [ 
    function() { 
     console.log("step1"); 
     fs.stat("f1.js",chain.shift()); 
    }, 
    function(err, stats) { 
     console.log("step2"); 
     fs.stat("f2.js",chain.shift()); 
    }, 
    function(err, stats) { 
     console.log("step3"); 
     fs.stat("f2.js",chain.shift()); 
    }, 
    function(err, stats) { 
     console.log("step4"); 
     fs.stat("f2.js",chain.shift()); 
    }, 
    function(err, stats) { 
     console.log("step5"); 
     fs.stat("f2.js",chain.shift()); 
    }, 
    function(err, stats) { 
     console.log("done"); 
    }, 
]; 
chain.shift()(); 

可以擴展成語並行處理,甚至並行的流程鏈:

var fs = require("fs"); 
var fork1 = 2, fork2 = 2, chain = [ 
    function() { 
     console.log("step1"); 
     fs.stat("f1.js",chain.shift()); 
    }, 
    function(err, stats) { 
     console.log("step2"); 
     var next = chain.shift(); 
     fs.stat("f2a.js",next); 
     fs.stat("f2b.js",next); 
    }, 
    function(err, stats) { 
     if (--fork1) 
      return; 
     console.log("step3"); 
     var next = chain.shift(); 

     var chain1 = [ 
      function() { 
       console.log("step4aa"); 
       fs.stat("f1.js",chain1.shift()); 
      }, 
      function(err, stats) { 
       console.log("step4ab"); 
       fs.stat("f1ab.js",next); 
      }, 
     ]; 
     chain1.shift()(); 

     var chain2 = [ 
      function() { 
       console.log("step4ba"); 
       fs.stat("f1.js",chain2.shift()); 
      }, 
      function(err, stats) { 
       console.log("step4bb"); 
       fs.stat("f1ab.js",next); 
      }, 
     ]; 
     chain2.shift()(); 
    }, 
    function(err, stats) { 
     if (--fork2) 
      return; 
     console.log("done"); 
    }, 
]; 
chain.shift()(); 
0

Task.js爲您提供這樣的:

spawn(function*() { 
    try { 
     var [foo, bar] = yield join(read("foo.json"), 
            read("bar.json")).timeout(1000); 
     render(foo); 
     render(bar); 
    } catch (e) { 
     console.log("read failed: " + e); 
    } 
}); 

取而代之的是:

var foo, bar; 
var tid = setTimeout(function() { failure(new Error("timed out")) }, 1000); 

var xhr1 = makeXHR("foo.json", 
        function(txt) { foo = txt; success() }, 
        function(err) { failure() }); 
var xhr2 = makeXHR("bar.json", 
        function(txt) { bar = txt; success() }, 
        function(e) { failure(e) }); 

function success() { 
    if (typeof foo === "string" && typeof bar === "string") { 
     cancelTimeout(tid); 
     xhr1 = xhr2 = null; 
     render(foo); 
     render(bar); 
    } 
} 

function failure(e) { 
    xhr1 && xhr1.abort(); 
    xhr1 = null; 
    xhr2 && xhr2.abort(); 
    xhr2 = null; 
    console.log("read failed: " + e); 
} 
6

我見過的最簡單的語法糖就是節點承諾。

npm install node-promise ||混帳克隆https://github.com/kriszyp/node-promise

使用這個你可以鏈異步方法爲:

firstMethod().then(secondMethod).then(thirdMethod); 

每個返回值是可作爲下一個參數。

1

我最近創建了一個簡單的抽象,名爲wait.for以同步模式(基於Fibers)調用異步函數。它處於早期階段,但有效。正是在:

https://github.com/luciotato/waitfor

使用wait.for,你可以調用任何標準異步的NodeJS功能,就好像它是一個同步功能。

使用wait.for你的代碼可能是:

var http=require('http'); 
var wait=require('wait.for'); 

http.createServer(function(req, res) { 
    wait.launchFiber(handleRequest,req, res); //run in a Fiber, keep node spinning 
}).listen(8080); 


//in a fiber 
function handleRequest(req, res) { 
    res.writeHead(200, {'Content-Type': 'text/html'}); 
    var html = "<h1>Demo page</h1>"; 
    var someData = wait.for(getSomeDate,client); 
    html += "<p>"+ someData +"</p>"; 
    var someOtherData = wait.for(getSomeOtherDate,client); 
    html += "<p>"+ someOtherData +"</p>"; 
    var moreData = wait.for(getMoreData,client); 
    html += "<p>"+ moreData +"</p>"; 
    res.write(html); 
    res.end(); 
}; 

...或者,如果你想成爲更簡潔(並添加錯誤捕獲)

//in a fiber 
function handleRequest(req, res) { 
    try { 
    res.writeHead(200, {'Content-Type': 'text/html'}); 
    res.write(
    "<h1>Demo page</h1>" 
    + "<p>"+ wait.for(getSomeDate,client) +"</p>" 
    + "<p>"+ wait.for(getSomeOtherDate,client) +"</p>" 
    + "<p>"+ wait.for(getMoreData,client) +"</p>" 
    ); 
    res.end(); 
    } 
    catch(err) { 
    res.end('error '+e.message); 
    } 

}; 

在所有getSomeDate,getSomeOtherDate and getMoreData 應該是隨着最後一個參數回調函數(ERR,數據)

標準異步功能,如:

function getMoreData(client, callback){ 
    db.execute('select moredata from thedata where client_id=?',[client.id], 
     ,function(err,data){ 
      if (err) callback(err); 
      callback (null,data); 
     }); 
} 
0

後,別人回答,你說,你的問題是局部變量。看起來一個簡單的方法是編寫一個外部函數來包含這些局部變量,然後使用一堆命名的內部函數並按名稱訪問它們。這樣,無論你需要鏈接多少個函數,你都只能嵌套兩個深度。

這裏是我的新手的使用時的mysql Node.js的模塊嵌套的嘗試:

function with_connection(sql, bindings, cb) { 
    pool.getConnection(function(err, conn) { 
     if (err) { 
      console.log("Error in with_connection (getConnection): " + JSON.stringify(err)); 
      cb(true); 
      return; 
     } 
     conn.query(sql, bindings, function(err, results) { 
      if (err) { 
       console.log("Error in with_connection (query): " + JSON.stringify(err)); 
       cb(true); 
       return; 
      } 
      console.log("with_connection results: " + JSON.stringify(results)); 
      cb(false, results); 
     }); 
    }); 
} 

以下是使用名爲內部函數重寫。外部函數with_connection也可以用作局部變量的持有者。(在這裏,我已經得到了參數sqlbindingscb以類似的方式行事,但你可以在with_connection定義了一些額外的局部變量。)

function with_connection(sql, bindings, cb) { 

    function getConnectionCb(err, conn) { 
     if (err) { 
      console.log("Error in with_connection/getConnectionCb: " + JSON.stringify(err)); 
      cb(true); 
      return; 
     } 
     conn.query(sql, bindings, queryCb); 
    } 

    function queryCb(err, results) { 
     if (err) { 
      console.log("Error in with_connection/queryCb: " + JSON.stringify(err)); 
      cb(true); 
      return; 
     } 
     cb(false, results); 
    } 

    pool.getConnection(getConnectionCb); 
} 

我一直在想,也許這將是可以使用實例變量創建一個對象,並使用這些實例變量來替代局部變量。但是現在我發現使用嵌套函數和局部變量的上述方法更簡單,更易於理解。它需要一些時間來忘掉面向對象,它看起來:-)

所以這裏是我的以前版本的對象和實例變量。

function DbConnection(sql, bindings, cb) { 
    this.sql = sql; 
    this.bindings = bindings; 
    this.cb = cb; 
} 
DbConnection.prototype.getConnection = function(err, conn) { 
    var self = this; 
    if (err) { 
     console.log("Error in DbConnection.getConnection: " + JSON.stringify(err)); 
     this.cb(true); 
     return; 
    } 
    conn.query(this.sql, this.bindings, function(err, results) { self.query(err, results); }); 
} 
DbConnection.prototype.query = function(err, results) { 
    var self = this; 
    if (err) { 
     console.log("Error in DbConnection.query: " + JSON.stringify(err)); 
     self.cb(true); 
     return; 
    } 
    console.log("DbConnection results: " + JSON.stringify(results)); 
    self.cb(false, results); 
} 

function with_connection(sql, bindings, cb) { 
    var dbc = new DbConnection(sql, bindings, cb); 
    pool.getConnection(function (err, conn) { dbc.getConnection(err, conn); }); 
} 

事實證明,bind可以用於一些優勢。它允許我擺脫我創建的有些醜陋的匿名函數,除了將自己轉向方法調用外,沒有做任何事情。我無法直接傳遞該方法,因爲它會涉及this的錯誤值。但與bind,我可以指定我想要的值this

function DbConnection(sql, bindings, cb) { 
    this.sql = sql; 
    this.bindings = bindings; 
    this.cb = cb; 
} 
DbConnection.prototype.getConnection = function(err, conn) { 
    var f = this.query.bind(this); 
    if (err) { 
     console.log("Error in DbConnection.getConnection: " + JSON.stringify(err)); 
     this.cb(true); 
     return; 
    } 
    conn.query(this.sql, this.bindings, f); 
} 
DbConnection.prototype.query = function(err, results) { 
    if (err) { 
     console.log("Error in DbConnection.query: " + JSON.stringify(err)); 
     this.cb(true); 
     return; 
    } 
    console.log("DbConnection results: " + JSON.stringify(results)); 
    this.cb(false, results); 
} 

// Get a connection from the pool, execute `sql` in it 
// with the given `bindings`. Invoke `cb(true)` on error, 
// invoke `cb(false, results)` on success. Here, 
// `results` is an array of results from the query. 
function with_connection(sql, bindings, cb) { 
    var dbc = new DbConnection(sql, bindings, cb); 
    var f = dbc.getConnection.bind(dbc); 
    pool.getConnection(f); 
} 

當然,這些都不是合適的JS與Node.js編碼 - 我只是花了幾個小時就可以了。但也許有點拋光這種技術可以幫助?

13

我很喜歡async.js這個用途。

的問題是由瀑布命令解決:

瀑布(任務,[回調])

運行的功能的陣列中的系列,每個傳遞它們的結果到陣列中的下一個。但是,如果任何函數向回調傳遞錯誤,則不執行下一個函數,並立即調用主回調並返回錯誤。

參數

任務 - 的功能來運行的數組,每個函數傳遞一個回調(ERR,RESULT1,結果2,...),則必須在完成呼叫。第一個參數是一個錯誤(可以爲null),並且任何其他參數都將作爲參數傳遞給下一個任務。 callback(err,[results]) - 一個可選的回調函數,一旦所有的函數完成就運行。這將傳遞最後一個任務回調的結果。

async.waterfall([ 
    function(callback){ 
     callback(null, 'one', 'two'); 
    }, 
    function(arg1, arg2, callback){ 
     callback(null, 'three'); 
    }, 
    function(arg1, callback){ 
     // arg1 now equals 'three' 
     callback(null, 'done'); 
    } 
], function (err, result) { 
    // result now equals 'done'  
}); 

至於REQ,RES變量,它們將在同一範圍內被共享作爲功能(REQ,RES){}包圍其整個async.waterfall呼叫。

不僅如此,異步非常乾淨。我的意思是,我改變這樣的很多的情況下:

function(o,cb){ 
    function2(o,function(err, resp){ 
     cb(err,resp); 
    }) 
} 

要第一:

function(o,cb){ 
    function2(o,cb); 
} 

然後向該:

function2(o,cb); 

然後向該:

async.waterfall([function2,function3,function4],optionalcb) 

它還允許準備許多預製功能從util.js非常快地調用async。只要把你想做的事情鏈接起來,確保o,cb得到普遍的處理。這加快了整個編碼過程。

1

爲了解決這個問題,我寫了nodent(https://npmjs.org/package/nodent),它無形地預處理了你的JS。你的示例代碼將變成(異步,真正閱讀文檔)。

http.createServer(function (req, res) { 
    res.writeHead(200, {'Content-Type': 'text/html'}); 
    var html = "<h1>Demo page</h1>"; 
    someData <<= getSomeDate(client) ; 

    html += "<p>"+ someData +"</p>"; 
    someOtherData <<= getSomeOtherDate(client) ; 

    html += "<p>"+ someOtherData +"</p>"; 
    moreData <<= getMoreData(client) ; 

    html += "<p>"+ moreData +"</p>"; 
    res.write(html); 
    res.end(); 
}); 

顯然,還有許多其他的解決方案,但前處理具有源地圖的支持很容易調試過少或沒有運行時開銷和感謝的優勢。

0

使用wire代碼的另一種方式是這樣的:

http.createServer(function (req, res) { 
    res.writeHead(200, {'Content-Type': 'text/html'}); 

    var l = new Wire(); 

    getSomeDate(client, l.branch('someData')); 
    getSomeOtherDate(client, l.branch('someOtherData')); 
    getMoreData(client, l.branch('moreData')); 

    l.success(function(r) { 
     res.write("<h1>Demo page</h1>"+ 
      "<p>"+ r['someData'] +"</p>"+ 
      "<p>"+ r['someOtherData'] +"</p>"+ 
      "<p>"+ r['moreData'] +"</p>"); 
     res.end(); 
    }); 
}); 
2

回調地獄能很容易在封閉的純JavaScript中作廢。下面的解決方案假定所有回調都遵循函數(錯誤,數據)簽名。

http.createServer(function (req, res) { 
    var modeNext, onNext; 

    // closure variable to keep track of next-callback-state 
    modeNext = 0; 

    // next-callback-handler 
    onNext = function (error, data) { 
    if (error) { 
     modeNext = Infinity; 
    } else { 
     modeNext += 1; 
    } 
    switch (modeNext) { 

    case 0: 
     res.writeHead(200, {'Content-Type': 'text/html'}); 
     var html = "<h1>Demo page</h1>"; 
     getSomeDate(client, onNext); 
     break; 

    // handle someData 
    case 1: 
     html += "<p>"+ data +"</p>"; 
     getSomeOtherDate(client, onNext); 
     break; 

    // handle someOtherData 
    case 2: 
     html += "<p>"+ data +"</p>"; 
     getMoreData(client, onNext); 
     break; 

    // handle moreData 
    case 3: 
     html += "<p>"+ data +"</p>"; 
     res.write(html); 
     res.end(); 
     break; 

    // general catch-all error-handler 
    default: 
     res.statusCode = 500; 
     res.end(error.message + '\n' + error.stack); 
    } 
    }; 
    onNext(); 
}); 
0

你知道考慮Jazz.js https://github.com/Javanile/Jazz.js/wiki/Script-showcase

 

    const jj = require('jazz.js'); 

    // ultra-compat stack 
    jj.script([ 
     a => ProcessTaskOneCallbackAtEnd(a), 
     b => ProcessTaskTwoCallbackAtEnd(b), 
     c => ProcessTaskThreeCallbackAtEnd(c), 
     d => ProcessTaskFourCallbackAtEnd(d), 
     e => ProcessTaskFiveCallbackAtEnd(e), 
    ]);