避免Node.js中回調地獄


為了解決這個阻塞問題,JavaScript嚴重依賴於回調,這是在長時間運行的進程(IO,定時器等)完成后運行的函數,因此允許代碼執行經過長時間運行的任務。

downloadFile('example.com/weather.json', function(err, data) {  
    console.log('Got weather data:', data);
});

  但是,問題來了,回調地獄

雖然回調的概念在理論上是巨大的,但它可能導致一些真正令人困惑和難以閱讀的代碼。 想象一下,如果你需要在回調后進行回調:

getData(function(a){  
    getMoreData(a, function(b){
        getMoreData(b, function(c){ 
            getMoreData(c, function(d){ 
                getMoreData(d, function(e){ 
                    ...
                });
            });
        });
    });
});

你可以看到,這真的是一發不可收拾。 拋出一些if語句,for循環,函數調用或注釋,你會有一些非常難讀的代碼。 初學者特別是這個的受害者,不理解如何避免這個“金字塔的厄運”。

這種層層嵌套的代碼給開發帶來了很多問題,主要體現在:

1.代碼可能性變差
2.調試困難
3.出現異常后難以排查

解決方案

design around it 

因此,許多程序員都陷入了回調地獄,因為這個(糟糕的設計)。 他們並沒有真正考慮他們的代碼結構提前,沒有意識到他們的代碼有多糟糕,意識到已經太晚了。 和你寫的任何代碼一樣,你應該停下來思考可以做什么,使它在編寫它之前或之后更簡單,更可讀。 這里有幾個提示,你可以用來避免回調地獄(或至少管理它)。

Give your functions names

當讀取代碼(特別是亂碼,無組織的代碼)時,它很容易失去邏輯流,甚至語法,當小空間擁塞這么多嵌套回調。 幫助打擊這一點的一個方法是命名你的函數,所以你需要做的是看一下名字,你會有一個更好的主意,它做什么。 它也給你的眼睛一個語法參考點。

考慮下面的代碼:

var fs = require('fs');

var myFile = '/tmp/test';  
fs.readFile(myFile, 'utf8', function(err, txt) {  
    if (err) return console.log(err);

    txt = txt + '\nAppended something!';
    fs.writeFile(myFile, txt, function(err) {
        if(err) return console.log(err);
        console.log('Appended text!');
    });
});

看看這可能需要幾秒鍾來實現每個回調的作用和它開始的地方。 向函數中添加一些額外的信息(名稱)會對可讀性產生重大影響,尤其是在回調中有多個級別時:

var fs = require('fs');

var myFile = '/tmp/test';  
fs.readFile(myFile, 'utf8', function appendText(err, txt) {  
    if (err) return console.log(err);

    txt = txt + '\nAppended something!';
    fs.writeFile(myFile, txt, function notifyUser(err) {
        if(err) return console.log(err);
        console.log('Appended text!');
    });
});

現在只是快速瀏覽會告訴你第一個函數附加一些文本,而第二個函數通知用戶的變化。

Declare your functions beforehand

 減少代碼雜亂的最好方法之一是保持更好的代碼分離。 如果你事先聲明一個回調函數並稍后調用它,你將避免深層嵌套的結構,這使得回調很難使用。

So you could go from this...

var fs = require('fs');

var myFile = '/tmp/test';  
fs.readFile(myFile, 'utf8', function(err, txt) {  
    if (err) return console.log(err);

    txt = txt + '\nAppended something!';
    fs.writeFile(myFile, txt, function(err) {
        if(err) return console.log(err);
        console.log('Appended text!');
    });
});

...to this:

var fs = require('fs');

function notifyUser(err) {  
    if(err) return console.log(err);
    console.log('Appended text!');
};

function appendText(err, txt) {  
    if (err) return console.log(err);

    txt = txt + '\nAppended something!';
    fs.writeFile(myFile, txt, notifyUser);
}

var myFile = '/tmp/test';  
fs.readFile(myFile, 'utf8', appendText);  

雖然這可以是一個很好的方式來幫助緩解問題,但它並不能完全解決問題。 當讀取以這種方式編寫的代碼時,如果你不記得每個函數的確切位置,那么你必須回去查看每個函數,以回溯邏輯流程,這可能需要時間。

Use modules

在幾乎每種編程語言中,降低復雜性的最好方法之一是模塊化。 JavaScript也不例外。 每當你編寫代碼時,花一些時間來回顧一下你是否經常遇到一個常見的模式。

你在不同的地方多次寫相同的代碼嗎? 你的代碼的不同部分是否遵循一個共同的主題? 如果是這樣,你有機會清理東西,抽象和重用代碼。

有數千個模塊,你可以看看供參考,但這里有幾個要考慮。 它們處理常見的,但非常具體的任務,否則會擾亂你的代碼並降低可讀性:Pluralize,csv,qs,clone。

Here is a new file called formuploader.js that contains our two functions from before:

module.exports.submit = formSubmit

function formSubmit (submitEvent) {
  var name = document.querySelector('input').value
  request({
    uri: "http://example.com/upload",
    body: name,
    method: "POST"
  }, postResponse)
}

function postResponse (err, response, body) {
  var statusMessage = document.querySelector('.status')
  if (err) return statusMessage.value = err
  statusMessage.value = body
}

Now that we have formuploader.js (and it is loaded in the page as a script tag after being browserified) we just need to require it and use it! Here is how our application specific code looks now:

var formUploader = require('formuploader')
document.querySelector('form').onsubmit = formUploader.submit

Async.js

幸運的是,像Async.js這樣的庫存在嘗試和遏制這個問題。 Async在你的代碼之上添加了一層函數,但可以通過避免回調嵌套大大降低復雜性。

許多輔助方法存在於Async中,可以在不同的情況下使用,例如系列,並行,瀑布等。每個函數都有一個特定的用例,所以花一些時間來了解哪個在哪些情況下會有幫助。

與Async一樣好,像什么,它不完美。 它很容易結合series, parallel,waterfall, forever, etc,在這一點你回到你開始與凌亂的代碼。 注意不要過早優化。 只是因為一些異步任務可以並行運行並不總是意味着他們應該。 實際上,由於Node只有單線程,因此使用Async時並行運行任務的性能增益很少甚至沒有。

上面的代碼可以使用Async的瀑布簡化:(前一個函數的回調會作為后一個函數的參數,如果有任何任務通過一個錯誤的回調,下一個函數不執行)

var fs = require('fs');  
var async = require('async');

var myFile = '/tmp/test';

async.waterfall([  
    function(callback) {
        fs.readFile(myFile, 'utf8', txt);
    },
    function(txt, callback) {
        txt = txt + '\nAppended something!';
        fs.writeFile(myFile, txt, callback);
    }
], function (err, result) {
    if(err) return console.log(err);
    console.log('Appended text!');
});

Promises

雖然Promises可以花費一些時間來掌握,但在我看來,它們是您可以在JavaScript中學習的更重要的概念之一。 它不僅大大減少了代碼行數,而且使代碼的邏輯流程更容易遵循。

這里是一個使用非常快,非常受歡迎的Promise庫,Bluebird的例子:

var Promise = require('bluebird');  
var fs = require('fs');  
Promise.promisifyAll(fs);

var myFile = '/tmp/test';  
fs.readFileAsync(myFile, 'utf8').then(function(txt) {  
    txt = txt + '\nAppended something!';
    fs.writeFile(myFile, txt);
}).then(function() {
    console.log('Appended text!');
}).catch(function(err) {
    console.log(err);
});

請注意,這個解決方案不僅比以前的解決方案更短,而且更容易閱讀(盡管,誠然,Promise風格的代碼可能需要一些習慣)。 花時間學習和理解承諾,這將是值得你的時間。 但是,Promise絕對不是解決我們在異步編程中的所有問題,所以不要假設通過使用它們,你會有一個快速,干凈,無bug的應用程序。 關鍵是知道什么時候對你有用。

一些Promise庫你應該檢查是Q,bluebird,或內置Promises如果你使用ES6的話。

Async/Await

 注意:這是一個ES7功能,目前不支持Node或io.js。 但是,你現在可以使用它像Babel一樣的轉換器。

清除代碼的另一個選項是我最近喜歡的(當它有更廣泛的支持時),它使用異步函數。 這將允許你編寫看起來更像同步代碼,但仍然是異步的代碼。

An example:

async function getUser(id) {  
    if (id) {
        return await db.user.byId(id);
    } else {
        throw 'Invalid ID!';
    }
}

try {  
    let user = await getUser(123);
} catch(err) {
    console.error(err);
}

The db.user.byId(id) call returns a Promise, which we'd normally have to use with .then(), but with await we can return the resolved value directly.

Notice that the function containing the await call is prefixed with async, which tells us that it contains asynchronous code and must also be called with await.

Another big advantage to this method is we can now use try/catchfor, and while with our asynchronous functions, which is much more intuitive than chaining promises together.

Aside from using transpilers like Babel and Traceur, you can also get functionality like this in Node with the asyncawait package.

避免這樣的常見問題,如回調地獄不容易,所以不要期待立即結束你的挫折。 我們都陷入了它。 只是嘗試減慢,花一些時間來思考你的代碼的結構。 像任何事情,實踐使完美。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM