問題描述
在開發過程中,遇到一個需求:在系統初始化時通過http獲取一個第三方伺服器端的列表,第三方伺服器提供了一個介面,可通過分頁形式獲取列表。
這裡有兩個問題:
- 未知的列表數量。就算已知總數量,如果資料量巨大,也不應該一次獲取全部資訊。
- 在node.js中,http是非同步的。
假設該介面為:
GET http://www.example.com/api/list
QUERY page=PAGE_INDEX&size=PAGE_SIZE
RETURN {data: [DATA,...], page: {page: PAGE_INDEX, size: PAGE_SIZE}}複製程式碼
期望結果:
所有的資料
[DATA, ...]複製程式碼
目的
藉以該問題,瞭解關於遞迴的更多知識。
解決方案
callback + 輔助變數
let request = require('superagent') let api = 'http://www.example.com/api/list' function getList (page, size, data = [], cb) { request(api).query({page, size}) .then(({body}) => { if (body.data && body.data.length) { // getList(page++, size, data.concat(body.data), cb) getList(++page, size, data.concat(body.data), cb) } else { cb(data) } }) } getList(1, 15, [], (data) => { console.log(data) })複製程式碼
還是能夠理解,不斷請求,並把獲得的資料傳到下一個請求;直到返回資料為空時終止,並返回累加的資料。就算不用promise而是普通回答(如使用end方法)也是可以的,可以不依賴函式返回值。但是需要一個沒有意義的變數。
promise
let request = require('superagent') let api = 'http://www.example.com/api/list' function getList (page, size) { return request(api).query({page, size}) .then(({body}) => { if (body.data && body.data.length) { return getList(++page, size) .then((data) => { return body.data.concat(data) }) } else { return [] } }) } getList(1, 15).then((data) => { console.log(data) })複製程式碼
相比較上面的方法,不在需要輔助變數和回撥函式,看起來更像普通的遞迴。但是由於promise中不斷return,使得程式碼難以理解,且容易忽略掉資料為空時else判斷。
generater
let request = require('superagent') let co = require('co') let api = 'http://www.example.com/api/list' function* getList (page, size) { let {body} = yield request(api).query({page, size}) if (body.data && body.data.length) { return body.data.concat(yield getList(++page, size)) } else { return [] } } co(function *() { let data = yield getList(1, 100) console.log(data.length); })複製程式碼
同步方式的程式碼,更加方便閱讀和理解。但是複雜的generater函式需要使用相應的環境才能執行,如co,最不喜歡的就是在陣列的一些遍歷方法中不能使用。
async + await 和generater差不多,也需要相應的環境,但是可以在陣列的遍歷方法中使用。
思考
對於一下這句話,我總是抱著懷疑的,因為很多時候我會很快想到使用遞迴可以解決,而不會想到使用迴圈來解決。在JavaScript中,那些非同步的遞迴函式怎麼才能轉換為迴圈呢?
遞迴和迴圈兩者完全可以互換
在一次面試中,面試官給我描述了這麼一個需求:
一個函式期望通過輸入兩個數,返回一個陣列,且不能使用迴圈。第一個引數x表示結果陣列中元素,第二個引數n代表陣列長度。
我第一反應就是使用遞迴,或者使用陣列的遍歷方法。說真的,我真的沒有那麼一丟丟的想到迴圈,然而迴圈貌似最應該容易想到...。
// 迴圈
function repeat (x, n) {
let arr = []
while (arr.length < n) {
arr.push(x)
}
return arr
}
// 遞迴
function repeat (x, n) {
return n > 0 ? [x].concat(repeat(x, --n)) : []
}
// 陣列
function repeat (x, n) {
new Array(n).fill(x)
}複製程式碼
那麼像上面那個請求的非同步遞迴,如果沒有ECMScript 2015(使用回撥),該怎麼寫成迴圈呢?
經典廣告詞:想不出來