2017-06-20 57 views
1

這裏是Node.js的新增內容。我正在尋找從另一個函數中調用N個異步API調用的正確方法,並將它們的結果組合到更下游。在我的情況下,N會相當小,阻止執行不會太差。結合多個Node.js API調用的結果

在同步執行中,combine()的實現應該可以工作。

如果我只需要一個API調用的結果,那麼在提供給callAPI()的回調函數中實現以下邏輯將很簡單。當我在執行foo之前需要結合所有結果(總數,[... args])時,我偶然發現。

我看着async.whilst,但無法讓它工作。我懷疑這是否符合我的需求。我也看過Promises這似乎是正確的領先優勢,但在爬進那個海綿狀的兔洞之前得到保證是件好事。是不是Promises是正確的方式,哪個模塊是在Node.js項目中使用的標準?

var http = require('http'); 

function callAPI(id) { 
    var options = { 
     host: 'example.com', 
     path: '/q/result/'.concat(id) 
    } 

    var req = http.get(options, (res) => { 
     var body = []; 
     res.on('data', (chunk) => { 
      body.push(chunk); 
     }).on('end',() => { 
      body = Buffer.concat(body).toString(); 
      return body; 
     }).on('error', (err) => { 
      console.error(err); 
     }); 
    }); 
} 

function combine(inputs) { 

    var total = 0; 
    for (i=0; i < inputs.length; i++) { 
     total += callAPI(inputs[i]['id']); 
    }; 
    console.log(total); 

    // call some function, foo(total, [...args]) 
} 

編輯1:

我試圖按照以下samanime的答案,修改API調用返回的承諾。參見:

function callAPI(id) { 
    return Promise((resolve, reject) => { 
     var options = { 
      host: 'example.com', 
      path: '/q/result/'.concat(id) 
     } 

     var req = http.get(options, (res) => { 
      var body = []; 
      res.on('data', (chunk) => { 
       body.push(chunk); 
      }).on('end',() => { 
       body = Buffer.concat(body).toString(); 
       resolve(body); 
      }).on('error', (err) => { 
       reject(err); 
      }); 
     }); 
    }); 
} 

function combine(inputs) { 

    var combined = []; 
    for (i=0; i < inputs.length; i++) { 
     total += callAPI(inputs[i]['id']); 
      .then(result => { 
       combined.push(result); 
      }); 
    }; 
    var total = combined.reduce((a, b) => a + b, 0); 
    console.log(total); 

    // call some function, foo(total, [...args]) 
} 

這似乎讓我在那裏一半。如果我在then()塊內,我可以看到用API調用的結果構建的列表。但是,我仍然無法在for循環的「結尾」訪問完整的combined。在建立完整列表後,我可以附加一個回調函數來運行嗎?有沒有更好的辦法?

編輯2(我的解決方案 - 每帕特里克·羅伯茨建議)

function callAPI(id) { 
    return Promise((resolve, reject) => { 
     var options = { 
      host: 'example.com', 
      path: '/q/result/'.concat(id) 
     } 

     var req = http.get(options, (res) => { 
      var body = []; 
      res.on('data', (chunk) => { 
       body.push(chunk); 
      }).on('end',() => { 
       body = parseInt(Buffer.concat(body)); 
       resolve(body); 
      }).on('error', (err) => { 
       reject(err); 
      }); 
     }); 
    }); 
} 

function combine(inputs) { 
    var combined = []; 
    Promise.all(inputs.map(input => callAPI(input.id))) 
     .then((combined) => { 
      var total = combined.reduce((a, b) => a + b, 0); 
      // foo(total, [...args]) 
     }); 
}; 
+0

試試這個https://開頭計算器。com/questions/44636542/loop-through-asynchronous-request/44636736#44636736它正是你在找什麼。 –

回答

0

你編輯正在尋找好了很多,但試試這個:

function callAPI(id) { 
    return Promise((resolve, reject) => { 
    var options = { 
     host: 'example.com', 
     path: '/q/result/' + id 
    } 

    http.get(options, (res) => { 
     var body = []; 
     res.on('data', (chunk) => { 
     body.push(chunk); 
     }).on('end',() => { 
     body = Buffer.concat(body).toString(); 
     resolve(body); 
     }).on('error', reject); 
    }); 
    }); 
} 

function combine(inputs) { 
    Promise.all(inputs.map(input => callAPI(input.id))).then((combined) => { 
    // completed array of bodies 
    console.log(combined); 
    // foo(combined.length, [...args]); 
    }).catch((error) => { 
    console.log(error); 
    }); 
} 
2

這聽起來像你可以鏈在一起了一堆承諾,沿傳遞數據。

基本上是這樣的:

const combined = []; 
asyncOne() 
    .then(result => { combined.push(result); return asyncTwo()) 
    .then(result => { combined.push(result); return asyncThree()) 
    // and so on 

只要每個函數返回一個承諾,你將所有設置。

如果你想在並行運行它們,使用Promise.all(),這將做同樣的事情對你:

Promise.all([asyncOne(), asyncTwo(), asyncThree() /* , etc */]) 
    .then(combined => /* combined is an array with the results of each */) 

這是迄今爲止對於這種事情的首選模式。

-1

我會添加一個跟蹤其餘API調用的計數器。每當API調用完成時,遞減,如果其值爲0,則表示完成。

const numCalls = 10; 
 
let remaining = numCalls; 
 
let data = []; 
 

 
function getRandomInt(min, max) { 
 
    min = Math.ceil(min); 
 
    max = Math.floor(max); 
 
    return Math.floor(Math.random() * (max - min)) + min; 
 
} 
 

 
function ajax() { 
 
    // Simulate ajax with a setTimeout for random amount of time. 
 
    setTimeout(() => { 
 
     // This is the callback when calling http.get 
 
     data.push(getRandomInt(0, 10)); // Some data from server 
 
     if (--remaining <= 0) { 
 
      // Am I the last call? Use data. 
 
      console.log(data); 
 
      console.log(data.length); 
 
     } 
 
    }, getRandomInt(1000, 3000)); 
 
} 
 

 
for (let i = 0; i < numCalls; i++) { 
 
    ajax(); 
 
}

+0

將應用程序邏輯與異步控制流邏輯混合在一起通常是不好的做法,這就是爲什麼'Promises'被創建的原因,並且在它們之前,像'async'這樣的庫。 –

+0

當然。這一切都取決於。如果可以的話,使用承諾。然而,我會爭辯說,在你的代碼中某些地方調用resolve/reject是異步控制流邏輯(但是很整潔),我們仍然將它與應用程序混合。邏輯。 – Peheje

+0

我正在談論_counting_內部回調,以及您的應用程序邏輯中消費者回調的有條件執行。當然,根據您選擇的異步控制流,您必須「resolve()」,「reject()」或「callback()」或「next()」,用你自己的控制流邏輯來創造輪子。 –