有沒優雅的寫法,讓nodejs的回調+循環不那麼操蛋?

nodejs回調和循環一起用,是非常痛苦的。 由於無法使用通常的for, while等控制流程,於是用類似遞歸的方式來「模擬」 循環控制。 實現起來就很操蛋,讓編程少了很多樂趣。 async.js庫的出現,讓程序員在面對此類問題時,稍微舒適了一點, 但總沒有原生的for, while好用和直接。 各位看官安有良策?

2014/10/14:

to anthonyive,想修改標題,題主不反對,麻煩別把意思改成正好相反好嗎?

原標題: 有沒優雅的寫法,讓nodejs的回調+循環不那麼操蛋?

anthonyive改的標題: 有沒優雅的寫法,讓nodejs的回調+循環不易使用?


現在是美東時間2017年2月26日晚上8點30分。不到24小時前Node.js 7.6.0發布,將內置的V8引擎版本更新到了5.5版本。這意味著Node.js歷史上首次在不需要開啟任何Flag的情況下,原生支持了ES7 Async/Await語法糖。也就是說,樓主,三年過去了,你要的解決方案終於來了。


第一

接受callback,如果內心抵觸到一個callback都不想要,這是你心態的問題。就像寫C程序不想和指針打交道,你應該學會尊重語言本身,接受這種編程風格。非同步和回調就想硬幣的兩面,JS作為非同步的語言,回調是很自然的模式,你不擁抱它,它也不會擁抱你。

哪怕有時候出現了嵌套的callback,也冷靜地想一想,事實是不是真的有那麼糟糕。很多時候其實未必,有些時候需要適當優化,也有時候不優化就是最好的優化。如果團隊里JS程序員沒有駕馭三層嵌套的能力,天天嚷嚷callback hell!是這些人水平不夠。

第二

重視簡單的技巧。很多時候你並不需要async,generator,co,甚至promise。你只需要適當地「代碼提取封裝「,就能管理好相當多的回調場景。但是現在程序員淹沒在各種高級的概念里,連基本的提取封裝都不會了。

「實事求是「。


有generator來優化非同步流程的方法: Generator與非同步編程

還有現成的庫: visionmedia/co · GitHub

不過generator在nodejs中現在還處於測試版本中,還沒有正式發布

現有的解決方案,除了async,還可以用promise模式來優化非同步流程,比如 kriskowal/q · GitHub


async/await (注意不是async.js庫)

升級到7.6已原生支持async語法


1. Promise標準 可以解決回調地獄的問題

2. js本身支持forEach,map和reduce,這三個都是歷遍的意思,但返回結果不一樣,forEach是什麼都不返回,map是把歷遍返回值形成一個數組,reduce是把前一個返回值導入和現在這個值放在一起處理,整個歷遍後形成一個值。

3.filter,歷遍所有值把返回值(符合值)給保留下來

4.看看underscore的庫,裡面有很多有用的工具,比如compose, 有f1(x),f2(x)和f3(x))三個函數,

_.compose(f1,f2,f3)返回一個f1(f2(f3))的函數組合函數。

5.按需求,使用特殊數據結構類型的數據,Hash,Map,Set,List,Array,Btree等等。

6.找符合需求,可以簡化代碼的第三方庫,

7.多學學和使用js的設計模式


co


先把Node.js升級到v7.6,然後他就完全支持ES2016的特性。

關於自己的看法,治node偶爾會干擾工作的非同步要用同步的方法。es2016的async/await加上es2015的promise加起來我覺得已經能夠解決一部分操蛋的循環加回調了。(而且我覺得promise和async/await才是天設一對!)

先把要循環的帶回調的函數整成promise對象,然後在循環的那個函數上用async,比如

async function () {}

然後在for循環內用await運行這個promise函數,就能做到必須一次循環完全做完之後再接下一次循環。

let panda = async function() {
function koala() {
return new Promise(...);
}
for (;;) {
await koala();
}
}

而且有一點就是 await必須在async的上下文中,和await必須用來運行帶promise的函數。不然的話前者是一個錯誤,後者會直接運行不考慮等待。

對於自己第一次用p/a+的心得我在我的博客有寫過一篇~


livescript


Generator (co / yield)

await / async

上面兩種都可以解決,然後最終歸為 Promise 的問題。


前面一些回答很好了。解決類似問題的庫還有:

- tildeio/rsvp.js 路 GitHub

- marcello3d/node-waiter · GitHub

Promise 的API不錯的。

Promise - JavaScript


async就夠用了吧, 有很複雜的循環邏輯么?


如果你想讓一堆promise挨個執行(因為他們之前存在依賴關係)你可以用promise.reduce 如果想讓他們一起執行可以用promise.all 為什麼想不開去寫循環呢


這種問題不是應該去stack overflow上問更好嗎?

上面看到Async, Promise,這些都是當年常用的工具,不然callback真的會死人的!Async用起來也還是相對比較麻煩的,當然裡面的waterfull(逐個任務依次執行),forEach(對數組裡每一個對象執行),foreachSeries(依次對每一個對象執行),ofLimit(依次執行x個對象,逐批處理,很適合資料庫任務批量讀寫)。Promise也是不錯的選擇。

但相對來說,co更加直觀,可以把他視為非同步命令逐行執行,非常符合原來寫Java,Python,C++的這些朋友, 如果你使用在新的項目,果斷切到最新stable 版本

引入 co,之後基本上可以按照Java,Python這樣的感覺走了:

const a = "htps://www.baidu.com";
const b = "https://www.zhihu.com";

let foo = (a, b) =&> { // ES6 語法,函數聲明:function(a, b) { } 等同於 (a, b) =&> { }
return co(function*() {
let ra = yield asyncRequest(a); // yield 會等待這個非同步任務執行完,然後return結果
let rb = yield asyncRequest(b);
return "OK";
})

foo(a, b);

說實話,使用es6會讓從java, C++ 等逐行執行語言轉過來的夥伴感覺好很多,但是依然還是有些不便,比如有時候你真的不知道什麼時候for循環就需要等待,有些就剛好不用,確實Egg Pain。

經驗上來說,Request,File IO,資料庫操作,比較複雜的大型數據邏輯等都可以保險起見視為非同步操作,需要專門做callback,promise或者yield之類的等待處理。希望之後JavaScript能夠有更人性化的改進。

既然你提到了逐個循環執行,那我就安利一下我的 co-foreach-series 這個包,原理就是,使用 co語法的 foreach,並且裡面的任務是逐個執行,要知道 foreach 是有可能平行執行的,如果你需要逐個執行的話就選他了。這個包主要是支持 co語法,函數裡面果斷使用 yield 爽得很!

npm install co-foreach-series --save 就ok了!

怎麼用? 谷歌 co-foreach-series 不出意外第一個,詳見co-foreach-series或者GitHub - wenqingyu/co-forEachSeries: forEachSeries by using es6 generator co.上有教程!


我作為C++半路出家寫前端的新手,來回答下這個問題吧。本人之前一直做底層C++開發,包括WebKit,JavaScripCore, Google V8,Node.js libuv相關的開發。

首先,我來貼一個我之前看到過的一個代碼片段吧。

deleteOldFile: function(callback) {
removeFile("/usr/local/opt/f1", function() {
removeFile("/usr/local/opt/f2", function() {
removeFile("/usr/local/opt/f3", function() {
removeFile("/usr/local/opt/f4", function() {
removeFile("/usr/local/opt/f5", function() {
removeFile("/usr/local/opt/f6", function() {
removeFile("/usr/local/opt/f7", function() {
removeFile("/usr/local/opt/f8", function() {
removeFile("/usr/local/opt/f9", function() {
removeFile("/usr/local/opt/f10", function() {
removeFile("/usr/local/opt/f11", function() {
removeFile("/usr/local/opt/f12", function() {
removeFile("/usr/local/opt/f13", function() {
removeFile("/usr/local/opt/f14", function() {
removeFile("/usr/local/opt/f15", function() {
removeFile("/usr/local/opt/f16", function() {
removeFile("/usr/local/opt/f17", function() {
removeFile("/usr/local/opt/f18", function() {
removeFile("/usr/local/opt/f19", function() {
removeFile("/usr/local/opt/f20", function() {
removeFile("/usr/local/opt/f21", function() {
callback callback();
});
});
});
});
});
});
});
});
});
});
});
});
});
});
});
});
});
});
});
});
});
function removeFile() {
//xxx
}
}

一點都不誇張。

我當時看到的時候,就是這個樣子。當我看到這段代碼的時候,我心裡其實是拒絕的,傷害我對程序員的職業操守的堅持。毀了JavaScript回調的特性在我心目中的形象。我當時也很怕我的代碼會寫成這個樣子。但是,Node.js本身很多API的預發風格,的確就是這樣,function(param, callback) {}這種風格。

首先,我們必須要從心裡上去接受回調函數,畢竟回調函數還是很多優點的,比如回調函數的調用執行速度要比普通的函數快,可以參考下這裡的介紹 Efficient JavaScript。我這裡說的回調函數特指匿名回調函數。當然回調函數也會有缺點,除了嵌套層級太深醜陋以外,匿名回調函數還有很多缺點,比如,容易造成內存泄漏。因為本人曾今開發過瀏覽器,需要深入WebKit做一些問題的修復和優化,其中遇到最頭疼的問題之一,就是匿名函數寫的不好導致引用關係錯亂而不被釋放,V8沒辦法對這類JavaScript對象進行GC,導致內存泄漏,重複的刷新同一個頁面,執行到該函數之後,內存都會增長。

如果想要代碼裡面一個回調都沒有,那這屬於心裡變態。我看到前面有幾位也回答了,關於,如何優雅的寫Nodejs的回調和循環。我的推薦也是Promise,co, generator。回調和循環,可以使用Promise.all來代替,也可以使用generator的while和yield來完成。但是,這其中也有一些需要注意的地方。Promise, generator以及回調,要相互配合使用。

首先說Promise。Promise如果使用的不好,最終的效果跟我上面貼出來的是一樣,只不過是then的嵌套。首先要搞懂,Promise的設計意圖是什麼。Promise我們可以理解為一個狀態機,這個狀態機有2中狀態,一種是resolved,另一個中就是rejected。如果完成一個任務,本來就是無狀態的,那就沒有必要使用Promise了。對於使用Promise來實現循環的關鍵點在於,Promise.all這類循環強調的是並發,Promise.all裡面的Promise數組任務可以認為是同時一起執行。

另外就是generator。generator實現循環的效果,就跟我們普通的C語言,Java裡面的for,while循環是一樣的,強調的是串列,必須等待一個任務執行完之後,才能執行循環裡面的下一個任務。

任何一種手段都不能偏信,一味的最求代碼使用Promise,或者generator,最終的效果都適得其反,無論是從代碼的可讀性還是代碼的易用性了來講,都不會太好。


微軟的WinJS有個promise類,有個方法promise.join(arrayOfPromise) 借鑒那樣的實現可以輕鬆很多。是在Github上開源的。


State Threads 回調終結者


分頁阅读: 1 2