在開發過程當中,遇到一個需求:在系統初始化時經過http獲取一個第三方服務器端的列表,第三方服務器提供了一個接口,可經過分頁形式獲取列表。javascript
這裏有兩個問題: java
假設該接口爲:node
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, ...]複製代碼
藉以該問題,瞭解關於遞歸的更多知識。api
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
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 async
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(使用回調),該怎麼寫成循環呢?
經典廣告詞:想不出來