2016-11-23 143 views
6

我有一個異步遞歸函數返回承諾,如果有更多的工作要做或返回結果數組,否則。在不涉及遞歸的情況下,它正確地返回數組,但是當遞歸存在時,數組是未定義的。該代碼是JavaScript承諾遞歸

function foo(filepath) { 

    var resultArr = []; 

    function doo(file) { 
     return asyncOperation(file).then(resp => { 
      resultArr.push(resp.data); 
      if (resp.pages) { 
       var pages = resp.pages.split(','); 
       pages.forEach(page => { 
        return doo(page); 
       }); 
      } else { 
       return resultArr; 
      } 
     }); 
    } 

    return doo(filepath); 
} 

這被稱爲

foo(abcfile).then(function(result){ 
    console.log(result); 
}); 

如果我通過abcfile它沒有resp.pages的方式,我得到的結果數組,但也有resp.pages,那麼結果數組未定義。

+0

你對「頁面」數據有什麼期望?你不會在'if(resp.pages)'塊中返回任何東西,並且在傳給'forEach'的函數中使用的返回不起任何作用 – Phil

+1

當'resp.pages'爲真時,你不返回任何東西,這等於'返回undefined'。 – Leo

回答

-1

問題是當有頁面時,你不會返回任何東西。

function foo(filepath) { 

    var resultArr = []; 

    function doo(file, promises) { 
     let promise = asyncOperation(file).then(resp => { 
      resultArr.push(resp.data); 
      if (resp.pages) { 
       var pages = resp.pages.split(','); 
       var pagePromises = []; 
       pages.forEach(page => { 
        return doo(page, pagePromises); 
       }); 

       //Return the pages 
       return pagePromises; 
      } else { 
       return resultArr; 
      } 
     }); 

     //They want it added 
     if (promises) { promises.push(promise); } 

     //Just return normally 
     else { return promise; } 
    } 

    return doo(filepath); 
} 
+0

這不會等待遞歸異步操作 – Phil

+0

@Phil我更新了現在做你想做的事情。 –

3

我認爲你只是缺少if (resp.pages)塊內返回的承諾

if (resp.pages) { 
    return Promise.all(resp.pages.split(',').map(page => doo(page))) 
     .then(pagesArr => { 
      return resultArr.concat(...pagesArr) 
     }) 
} 

我想有可能是一個問題與作用域resultArrdoo功能外,所以也許嘗試此

function foo(filepath) { 
    function doo(file) { 
     return asyncOperation(file).then(resp => { 
      const resultArr = [ resp.data ] 
      if (resp.pages) { 
       return Promise.all(resp.pages.split(',').map(page => doo(page))) 
        .then(pagesArr => resultArr.concat(...pagesArr)) 
      } else { 
       return resultArr 
      } 
     }) 
    } 

    return doo(filePath) 
} 

要解釋擴散算子的使用,請看這種方式...

假設您有三個頁面的文件top; page1page2page3和各與一對夫婦的每個子頁那些做出決議,在pagesArr看起來像

[ 
    ['page1', 'page1a', 'page1b'], 
    ['page2', 'page2a', 'page2b'], 
    ['page3', 'page3a', 'page3b'] 
] 

resultArr到目前爲止看起來像

['top'] 

如果使用concat無傳播運營商,你最終與

[ 
    "top", 
    [ 
    "page1", 
    "page1a", 
    "page1b" 
    ], 
    [ 
    "page2", 
    "page2a", 
    "page2b" 
    ], 
    [ 
    "page3", 
    "page3a", 
    "page3b" 
    ] 
] 

但與sp讀,你會得到

[ 
    "top", 
    "page1", 
    "page1a", 
    "page1b", 
    "page2", 
    "page2a", 
    "page2b", 
    "page3", 
    "page3a", 
    "page3b" 
] 
+0

我應該使用concat來創建扁平列表.. – ams

+0

@Phil我認爲你的數組範圍可能與承諾一起 – ams

+0

@ams這就是爲什麼我把它移動到我的編輯 – Phil

0

這裏的問題是在if (resp.pages)分支混合異步/同步操作。基本上,如果您希望承諾鏈按預期工作,您必須從回調中返回承諾。

除了菲爾的回答,如果你想要執行的訂單/序列頁

if (resp.pages) { 
    var pages = resp.pages.split(','); 
    return pages.reduce(function(p, page) { 
    return p.then(function() { 
     return doo(page); 
    }); 
    }, Promise.resolve()); 
} 
+0

這非常酷,如果OP的'asyncOperation'不能同時執行多個請求,但是你沒有合併任何結果way – Phil

1

要驗證這工作,我會成爲一個fake數據集,以及fakeAsyncOperation從數據集讀取數據異步。要密切建模您的數據,來自假數據集的每個查詢將返回一個帶有datapages字段的響應。

let fake = new Map([ 
    ['root', {data: 'root', pages: ['a', 'b', 'c', 'd']}], 
    ['a',  {data: 'a',  pages: ['a/a', 'a/a']}], 
    ['a/a',  {data: 'a/a',  pages: []}], 
    ['a/b',  {data: 'a/b',  pages: ['a/b/a']}], 
    ['a/b/a', {data: 'a/b/a', pages: []}], 
    ['b',  {data: 'b',  pages: ['b/a']}], 
    ['b/a',  {data: 'b/a',  pages: ['b/a/a']}], 
    ['b/a/a', {data: 'b/a/a', pages: ['b/a/a/a']}], 
    ['b/a/a/a', {data: 'b/a/a/a', pages: []}], 
    ['c',  {data: 'c',  pages: ['c/a', 'c/b', 'c/c', 'c/d']}], 
    ['c/a',  {data: 'c/a',  pages: []}], 
    ['c/b',  {data: 'c/b',  pages: []}], 
    ['c/c',  {data: 'c/c',  pages: []}], 
    ['c/d',  {data: 'c/d',  pages: []}], 
    ['d',  {data: 'd',  pages: []}] 
]); 

let fakeAsyncOperation = (page) => { 
    return new Promise(resolve => { 
    setTimeout(resolve, 100, fake.get(page)) 
    }) 
} 

接下來我們有你的foo函數。我已將doo更名爲enqueue,因爲它的工作方式類似於隊列。它有兩個參數:acc用於跟蹤累計數據,xs(已解組),它是隊列中的項目。

我已經使用了新的async/await語法,這對於處理這個問題特別好。我們不必手動構建任何承諾或處理任何手動鏈接。

我做自由使用傳播語法的遞歸調用,因爲我的可讀性,但你可以很容易,如果你喜歡,更多的替換這些爲concat電話acc.concat([data])xs.concat(pages)。 - 這是函數式編程,所以只需選擇一個你喜歡的不可變操作並使用它。

最後,與其他使用Promise.all的答案不同,這將處理系列中的每個頁面。如果一個頁面有50個子頁面,則Promise.all會嘗試在並行中發出50個請求,這可能是不希望的。將程序從並行轉換爲串行不一定簡單,所以這就是提供這個答案的原因。

function foo (page) { 
    async function enqueue (acc, [x,...xs]) { 
    if (x === undefined) 
     return acc 
    else { 
     let {data, pages} = await fakeAsyncOperation(x) 
     return enqueue([...acc, data], [...xs, ...pages]) 
    } 
    } 
    return enqueue([], [page]) 
} 

foo('root').then(pages => console.log(pages))

輸出

[ 'root', 
    'a', 
    'b', 
    'c', 
    'd', 
    'a/a', 
    'a/a', 
    'b/a', 
    'c/a', 
    'c/b', 
    'c/c', 
    'c/d', 
    'b/a/a', 
    'b/a/a/a' ] 

備註

我很高興,我的解決方案的foo功能不是從原始的太遠了 - 我想你會明白, 。它們都使用內部輔助功能進行循環,並以類似的方式處理問題。 async/await使代碼保持良好的平坦性和高度可讀性(imo)。總的來說,我認爲這是一個有點複雜的問題的絕佳解決方案。

哦,不要忘了循環引用。在我的數據集中沒有循環引用,但是如果頁面'a'pages: ['b']'b'pages: ['a'],則可以預期無限遞歸。由於該答案能夠連續處理頁面,因此這將非常容易解決(通過檢查累積值acc獲取現有頁面標識符)。當並行處理頁面時,這是非常棘手的(並且這個答案超出了範圍)。

+0

很好的回答,很高興看到'async'和'await'成爲常態。我認爲OP的代碼試圖導航文件系統,所以如果我們忽略鏈接(符號鏈接),循環引用*可能不是問題。另外,您的StackOverflow格式化滑雪板不在鏈條上! – Phil

+0

感謝您的反饋,@菲爾^ _ ^ – naomik