2017-02-13 52 views
3

我有一個api調用,有時會返回分頁響應。我想自動將這些添加到我的承諾中,以便在所有數據都到達時收到回調。將Promise添加到Promise.all()

這是我的嘗試。我希望新的承諾被添加並且Promise.all一旦完成就解決。

究竟發生了什麼,Promise.all並沒有等待第二個請求。我的猜測是Promise.all在調用時會附加「聽衆」。

有沒有一種方法來「重塑」Promise.all()?

function testCase (urls, callback) { 
    var promises = []; 
    $.each(urls, function (k, v) { 
     promises.push(new Promise(function(resolve, reject) { 
      $.get(v, function(response) { 
       if (response.meta && response.meta.next) { 
        promises.push(new Promise(function (resolve, reject) { 
         $.get(v + '&offset=' + response.meta.next, function (response) { 
          resolve(response); 
         }); 
        })); 
       } 
       resolve(response); 
      }).fail(function(e) {reject(e)}); 
     })); 
    }); 

    Promise.all(promises).then(function (data) { 
     var response = {resource: []}; 
     $.each(data, function (i, v) { 
      response.resource = response.resource.concat(v.resource); 
     }); 
     callback(response); 
    }).catch(function (e) { 
     console.log(e); 
    }); 
} 

所需的流量是一樣的東西:

  1. 創建一組承諾。
  2. 一些承諾產生了更多的承諾。
  3. 一旦所有最初的承諾和產生的承諾都解決了,就調用回調函數。
+0

你在哪裏初始化'promises'?我看到你推動它,但我沒看到你創造它。 –

+0

什麼是'url'? (如果它是一個數組,通常是一個複數,例如'urls'。) –

+0

爲什麼'response.resource = response.resource.concat(v.resource);'?每次創建一個全新的陣列...? –

回答

3

它看起來像的總體目標是:

  1. 對於urls每個條目,請撥打$.get並等待它完成。
    • 如果返回只是沒有「未來」的響應,保持一個響應
    • 如果返回了「下一個」,我們要請「下一個」,以及再保持二者的響應。
  2. 當所有工作完成時用response調用回調。

我會改變#2,所以你只需返回承諾並用response來解決。

有關承諾的一個重要的事情是,then返回承諾,這將既可以與你then返回的內容(直接,如果你返回一個非thenable值,或間接,如果你返回thenable解決,通過奴役自己到那可以)。這意味着如果您有承諾來源(在這種情況下爲$.get),則幾乎不需要使用new Promise;只需使用您使用then創建的承諾。 (。而catch

看評論:

function testCase(urls) { 
    // Return a promise that will be settled when the various `$.get` calls are 
    // done. 
    return Promise.all(urls.map(function(url) { 
     // Return a promise for this `$.get`. 
     return $.get(url) 
      .then(function(response) { 
       if (response.meta && response.meta.next) { 
        // This `$.get` has a "next", so return a promise waiting 
        // for the "next" which we ultimately resolve (via `return`) 
        // with an array with both the original response and the 
        // "next". Note that since we're returning a thenable, the 
        // promise created by `then` will slave itself to the 
        // thenable we return. 
        return $.get(url + "&offset=" + response.meta.next) 
         .then(function(nextResponse) { 
          return [response, nextResponse]; 
         }); 
       } else { 
        // This `$.get` didn't have a "next", so resolve this promise 
        // directly (via `return`) with an array (to be consistent 
        // with the above) with just the one response in it. Since 
        // what we're returning isn't thenable, the promise `then` 
        // returns is resolved with it. 
        return [response]; 
       } 
      }); 
    })).then(function(responses) { 
     // `responses` is now an array of arrays, where some of those will be one 
     // entry long, and others will be two (original response and next). 
     // Flatten it, and return it, which will settle he overall promise with 
     // the flattened array. 
     var flat = []; 
     responses.forEach(function(responseArray) { 
      // Push all promises from `responseArray` into `flat`. 
      flat.push.apply(flat, responseArray); 
     }); 
     return flat; 
    }); 
} 

注意如何我們從不使用catch那裏。我們將錯誤處理推遲給調用者。

用法:

testCase(["url1", "url2", "etc."]) 
    .then(function(responses) { 
     // Use `responses` here 
    }) 
    .catch(function(error) { 
     // Handle error here 
    }); 

testCase函數看起來很長,但是這是評論,只是因爲。這裏是沒有它們的:

function testCase(urls) { 
    return Promise.all(urls.map(function(url) { 
     return $.get(url) 
      .then(function(response) { 
       if (response.meta && response.meta.next) { 
        return $.get(url + "&offset=" + response.meta.next) 
         .then(function(nextResponse) { 
          return [response, nextResponse]; 
         }); 
       } else { 
        return [response]; 
       } 
      }); 
    })).then(function(responses) { 
     var flat = []; 
     responses.forEach(function(responseArray) { 
      flat.push.apply(flat, responseArray); 
     }); 
     return flat; 
    }); 
} 

......如果我們使用ES2015的箭頭函數,它會更加簡潔。 :-)


在評論你問:

難道這處理,如果有下一個未來?就像第3頁的結果?

我們可以做到這一點通過封裝該邏輯到一個功能,我們使用,而不是$.get,我們可以遞歸使用:

function getToEnd(url, target, offset) { 
    // If we don't have a target array to fill in yet, create it 
    if (!target) { 
     target = []; 
    } 
    return $.get(url + (offset ? "&offset=" + offset : "")) 
     .then(function(response) { 
      target.push(response); 
      if (response.meta && response.meta.next) { 
       // Keep going, recursively 
       return getToEnd(url, target, response.meta.next); 
      } else { 
       // Done, return the target 
       return target; 
      } 
     }); 
} 

然後我們的主要testCase簡單:

function testCase(urls) { 
    return Promise.all(urls.map(function(url) { 
     return getToEnd(url); 
    })).then(function(responses) { 
     var flat = []; 
     responses.forEach(function(responseArray) { 
      flat.push.apply(flat, responseArray); 
     }); 
     return flat; 
    }); 
} 
+0

如果有下一個'next',可以處理嗎?就像第3頁的結果? – Josiah

+0

@Josiah:它*可以*,是的。我們必須改變邏輯。我們可能希望有一個處理給定URL的函數,並返回一個可以用所有「nexts」的響應數組解決的promise,並遞歸使用它。你想要傳遞一個URL和一個偏移或類似的東西。 –

+0

@Josiah:結果很簡單,我已經將它添加到答案的末尾。 –

1

假設您使用的是jQuery v3 +,則可以使用$.ajax返回的承諾傳遞給Promise.all()

你所缺少的就是返回第二個請求是一個承諾,而不是試圖把它推到承諾陣列

簡單的例子

var promises = urls.map(function(url) { 
    // return promise returned by `$.ajax` 
    return $.get(url).then(function(response) { 
    if (response.meta) { 
     // return a new promise 
     return $.get('special-data.json').then(function(innerResponse) { 
     // return innerResponse to resolve promise chain 
     return innerResponse; 
     }); 

    } else { 
     // or resolve with first response 
     return response; 
    } 
    }); 

}) 

Promise.all(promises).then(function(data) { 
    console.dir(data) 
}).catch(function(e) { 
    console.log(e); 
}); 

DEMO