如何在 for 循环(每次迭代都会产生一个承诺)完成后 return 单一承诺?
How to return single promise after for loop (which produces a promise on every iteration) is complete?
我的 promise return 代码有问题,我有一个函数 getTagQuotes
,它包含一个 for 循环,可以多次调用 API 到 return数据到数组中。
我的代码是如何开始的:
// If there are tags, then wait for promise here:
if (tags.length > 0) {
// Setting promise var to getTagQuotes:
var promise = getTagQuotes(tags).then(function() {
console.log('promise =',promise);
// This array should contain 1-3 tags:
console.log('tweetArrayObjsContainer =',tweetArrayObjsContainer);
// Loop through to push array objects into chartObj:
for (var i=0; i<tweetArrayObjsContainer.length; i++) {
chartObj.chartData.push(tweetArrayObjsContainer[i]);
}
// Finally draw the chart:
chartDirective = ScopeFactory.getScope('chart');
chartDirective.nvd3.drawChart(chartObj.chartData);
});
}
我的 getTagQuotes 函数带有承诺 return:
function getTagQuotes(tags) {
var deferred = $q.defer(); // setting the defer
var url = 'app/api/social/twitter/volume/';
// My for loop, which only returns ONCE, even if there are 3 tags
for (var i=0; i<tags.length; i++) {
var loopStep = i;
rawTagData = [];
// The return statement
return GetTweetVolFactory.returnTweetVol(url+tags[i].term_id)
.success(function(data, status, headers, config) {
rawTagData.push(data);
// One the last loop, call formatTagData
// which fills the tweetArrayObjsContainer Array
if (loopStep === (rawTagData.length - 1)) {
formatTagData(rawTagData);
deferred.resolve();
return deferred.promise;
}
});
}
function formatTagData(rawData) {
for (var i=0; i<rawData.length; i++) {
var data_array = [];
var loopNum = i;
for (var j=0; j<rawData[loopNum].frequency_counts.length; j++) {
var data_obj = {};
data_obj.x = rawData[loopNum].frequency_counts[j].start_epoch;
data_obj.y = rawData[loopNum].frequency_counts[j].tweets;
data_array.push(data_obj);
}
var tweetArrayObj = {
"key" : "Quantity"+(loopNum+1), "type" : "area", "yAxis" : 1, "values" : data_array
};
tweetArrayObjsContainer.push(tweetArrayObj);
}
}
}
注意这一行
return GetTweetVolFactory.returnTweetVol(url+tags[i].term_id)
它在我的 for 循环中:
for (var i=0; i<tags.length; i++)
如果我只需要循环一次,一切都很好。然而,一旦有另一个标签(最多 3 个),它仍然只有 return 第一个 loop/data。它不会等到 for 循环完成。然后 return 承诺。所以我的 tweetArrayObjsContainer
总是只有第一个标签。
你应该 return 这里的承诺数组,这意味着你应该像这样更改 getTagsQuotes :
function getTagQuotes(tags) {
var url = 'app/api/social/twitter/volume/',
promises = [];
for (var i=0; i<tags.length; i++) {
promises.push( GetTweetVolFactory.returnTweetVol( url+tags[i].term_id ) );
}
return promises;
}
然后像这样遍历这个承诺:
if (tags.length > 0) {
var promises = getTagQuotes(tags);
promises.map( function( promise ) {
promise.then( function( data ) {
//Manipulate data here
});
});
}
编辑: 如果您希望按照评论中的概述完成所有承诺,您应该这样做:
if (tags.length > 0) {
Promise.all( getTagQuotes(tags) ).then( function( data ) {
//Manipulate data here
});
}
编辑:完整数据操作:
Promise.all( getTagQuotes(tags) ).then( function( allData ) {
allData.map( function( data, dataIndex ){
var rawData = data.data,
dataLength = rawData.frequency_counts.length,
j = 0,
tweetArrayObj = {
// "key" : "Quantity"+(i+1),
// "color" : tagColorArray[i],
"key" : "Quantity",
"type" : "area",
"yAxis" : 1,
"values" : []
};
for ( j; j < dataLength; j++ ) {
rawData.frequency_counts[j].start_epoch = addZeroes( rawData.frequency_counts[j].start_epoch );
tweetArrayObj.values.push( { x:rawData.frequency_counts[j].start_epoch, y:rawData.frequency_counts[j].tweets } );
}
tweetArrayObjsContainer.push( tweetArrayObj );
});
for ( var i= 0,length = tweetArrayObjsContainer.length; i < length; i++ ) {
chartObj.chartData.push( tweetArrayObjsContainer[ i ] );
}
chartDirective = ScopeFactory.getScope('chart');
chartDirective.nvd3.drawChart(chartObj.chartData);
});
使用 deferreds 被广泛认为是一种反模式。如果您的 promise 库支持 promise 构造函数,这是创建您自己的 promise 的更简单方法。
我通常使用具有 all
函数的 promise 实现,而不是尝试解决所有 promise。然后我创建一个 returns 对某事物的承诺的函数,然后创建另一个 returns 对所有事物的承诺的函数。
使用 map()
函数通常也比使用 for
循环更简洁。
这是一个通用的食谱。假设您的 promise 实现具有某种 all
函数的风格:
var fetchOne = function(oneData){
//Use a library that returns a promise
return ajax.get("http://someurl.com/" + oneData);
};
var fetchAll = function(allData){
//map the data onto the promise-returning function to get an
//array of promises. You could also use `_.map` if you're a
//lodash or underscore user.
var allPromises = myData.map(fetchOne);
return Promise.all(allPromises);
};
var allData = ["a", "b", "c"];
var promiseForAll = fetchAll(allData);
//Handle the results for all of the promises.
promiseForAll.then(function(results){
console.log("All done.", results);
});
三期:
- 您没有 return 来自
getTagQuotes
方法的延迟承诺。
- 您正在查看
i
以查看您是否完成了循环,并且 for 循环甚至在第一次成功调用之前就已经完成 (i == (tags.length - 1)
)。
- 您在循环的第一次迭代中调用了
return
,以至于您甚至没有到达第二个项目。
这是更正后的代码(尚未测试)
function getTagQuotes(tags) {
var deferred = $q.defer(); // setting the defer
var url = 'app/api/social/twitter/volume/';
var tagsComplete = 0;
for (var i=0; i<tags.length; i++) {
rawTagData = [];
GetTweetVolFactory.returnTweetVol(url+tags[i].term_id)
.success(function(data, status, headers, config) {
rawTagData.push(data);
tagsComplete++;
if (tagsComplete === tags.length) {
formatTagData(rawTagData);
deferred.resolve();
}
});
}
return deferred.promise;
}
return deferred.promise;
应该是函数的 return 值,而不是 GetTweetVolFactory.returnTweetVol()
,因为那是你打算承诺的。
您的问题是您正在调用多个 GetTweetVolFactory.returnTweetVol()
,然后您需要合并所有这些异步调用以解决您的承诺。为此,您应该只承诺一个 GetTweetVolFactory.returnTweetVol()
调用:
function promisifiedTweetVol(rawTagData, urlStuff) {
var deferred = $q.defer(); // setting the defer
GetTweetVolFactory.returnTweetVol(urlStuff)
.success(function(data, status, headers, config) {
rawTagData.push(data);
// One the last loop, call formatTagData
// which fills the tweetArrayObjsContainer Array
if (loopStep === (rawTagData.length - 1)) {
formatTagData(rawTagData);
deferred.resolve();
}
});
return deferred.promise;
}
然后在循环中调用每个 promise 和 return 在所有 promise 完成时解析的 promise:
function getTagQuotes(tags) {
var url = 'app/api/social/twitter/volume/';
var promises = [];
// My for loop, which only returns ONCE, even if there are 3 tags
for (var i=0; i<tags.length; i++) {
var loopStep = if;
rawTagData = [];
promises.push( promisifiedTweetVol(rawTagData, url+tags[i].term_id) );
}
// ...
return $.when(promises);
}
您的代码还有一些问题,但您应该能够按照我的提示进行操作。
将每个承诺放入一个数组中,然后执行:
$q.all(arrayOfPromises).then(function(){
// this runs when every promise is resolved.
});
参考这个问题和 :
- 代码通常会在几个地方用 array.map() 代替
for
循环更清晰。
getTagQuotes()
将通过构建一系列承诺、将其提交给 $q.all()
并返回聚合承诺来变得更简洁。
formatTagData()
及其与调用者的关系将通过返回转换后的 rawData
. 变得更清晰
根据一些假设,代码应简化为如下所示:
getTagQuotes(tags).then(function(tweetArrayObjsContainer) {
chartObj.chartData = chartObj.chartData.concat(tweetArrayObjsContainer); // concat() ...
// chartObj.chartData = tweetArrayObjsContainer; // ... or simply assign??
chartDirective = ScopeFactory.getScope('chart');
chartDirective.nvd3.drawChart(chartObj.chartData);
});
function getTagQuotes(tags) {
var url = 'app/api/social/twitter/volume/';
var promises = tags.map(function(tag) {
var deferred = $q.defer();
GetTweetVolFactory.returnTweetVol(url + tag.term_id)
.success(function(data, status, headers, config) {
deferred.resolve(data);
})
.error(function(data, status) {
console.log(tag.term_id + ': error in returning tweet data');
deferred.resolve(null); // resolve() here effectively catches the error
});
return deferred.promise;
});
return $q.all(promises).then(formatTagData); //this is much much cleaner than building an explicit data array and resolving an outer deferred.
function formatTagData(rawData) {
return rawData.filter(function(data) {
return data || false; // filter out any nulls
}).map(function(item, i) {
return {
'key': 'Quantity' + (i+1),
'type': 'area',
'yAxis': 1,
'color': tagColorArray[i],
'values': item.frequency_counts.reverse().map(function(c) {
return {
x: addZeroes(c.start_epoch),
y: c.tweets,
};
})
};
});
}
}
我的 promise return 代码有问题,我有一个函数 getTagQuotes
,它包含一个 for 循环,可以多次调用 API 到 return数据到数组中。
我的代码是如何开始的:
// If there are tags, then wait for promise here:
if (tags.length > 0) {
// Setting promise var to getTagQuotes:
var promise = getTagQuotes(tags).then(function() {
console.log('promise =',promise);
// This array should contain 1-3 tags:
console.log('tweetArrayObjsContainer =',tweetArrayObjsContainer);
// Loop through to push array objects into chartObj:
for (var i=0; i<tweetArrayObjsContainer.length; i++) {
chartObj.chartData.push(tweetArrayObjsContainer[i]);
}
// Finally draw the chart:
chartDirective = ScopeFactory.getScope('chart');
chartDirective.nvd3.drawChart(chartObj.chartData);
});
}
我的 getTagQuotes 函数带有承诺 return:
function getTagQuotes(tags) {
var deferred = $q.defer(); // setting the defer
var url = 'app/api/social/twitter/volume/';
// My for loop, which only returns ONCE, even if there are 3 tags
for (var i=0; i<tags.length; i++) {
var loopStep = i;
rawTagData = [];
// The return statement
return GetTweetVolFactory.returnTweetVol(url+tags[i].term_id)
.success(function(data, status, headers, config) {
rawTagData.push(data);
// One the last loop, call formatTagData
// which fills the tweetArrayObjsContainer Array
if (loopStep === (rawTagData.length - 1)) {
formatTagData(rawTagData);
deferred.resolve();
return deferred.promise;
}
});
}
function formatTagData(rawData) {
for (var i=0; i<rawData.length; i++) {
var data_array = [];
var loopNum = i;
for (var j=0; j<rawData[loopNum].frequency_counts.length; j++) {
var data_obj = {};
data_obj.x = rawData[loopNum].frequency_counts[j].start_epoch;
data_obj.y = rawData[loopNum].frequency_counts[j].tweets;
data_array.push(data_obj);
}
var tweetArrayObj = {
"key" : "Quantity"+(loopNum+1), "type" : "area", "yAxis" : 1, "values" : data_array
};
tweetArrayObjsContainer.push(tweetArrayObj);
}
}
}
注意这一行
return GetTweetVolFactory.returnTweetVol(url+tags[i].term_id)
它在我的 for 循环中:
for (var i=0; i<tags.length; i++)
如果我只需要循环一次,一切都很好。然而,一旦有另一个标签(最多 3 个),它仍然只有 return 第一个 loop/data。它不会等到 for 循环完成。然后 return 承诺。所以我的 tweetArrayObjsContainer
总是只有第一个标签。
你应该 return 这里的承诺数组,这意味着你应该像这样更改 getTagsQuotes :
function getTagQuotes(tags) {
var url = 'app/api/social/twitter/volume/',
promises = [];
for (var i=0; i<tags.length; i++) {
promises.push( GetTweetVolFactory.returnTweetVol( url+tags[i].term_id ) );
}
return promises;
}
然后像这样遍历这个承诺:
if (tags.length > 0) {
var promises = getTagQuotes(tags);
promises.map( function( promise ) {
promise.then( function( data ) {
//Manipulate data here
});
});
}
编辑: 如果您希望按照评论中的概述完成所有承诺,您应该这样做:
if (tags.length > 0) {
Promise.all( getTagQuotes(tags) ).then( function( data ) {
//Manipulate data here
});
}
编辑:完整数据操作:
Promise.all( getTagQuotes(tags) ).then( function( allData ) {
allData.map( function( data, dataIndex ){
var rawData = data.data,
dataLength = rawData.frequency_counts.length,
j = 0,
tweetArrayObj = {
// "key" : "Quantity"+(i+1),
// "color" : tagColorArray[i],
"key" : "Quantity",
"type" : "area",
"yAxis" : 1,
"values" : []
};
for ( j; j < dataLength; j++ ) {
rawData.frequency_counts[j].start_epoch = addZeroes( rawData.frequency_counts[j].start_epoch );
tweetArrayObj.values.push( { x:rawData.frequency_counts[j].start_epoch, y:rawData.frequency_counts[j].tweets } );
}
tweetArrayObjsContainer.push( tweetArrayObj );
});
for ( var i= 0,length = tweetArrayObjsContainer.length; i < length; i++ ) {
chartObj.chartData.push( tweetArrayObjsContainer[ i ] );
}
chartDirective = ScopeFactory.getScope('chart');
chartDirective.nvd3.drawChart(chartObj.chartData);
});
使用 deferreds 被广泛认为是一种反模式。如果您的 promise 库支持 promise 构造函数,这是创建您自己的 promise 的更简单方法。
我通常使用具有 all
函数的 promise 实现,而不是尝试解决所有 promise。然后我创建一个 returns 对某事物的承诺的函数,然后创建另一个 returns 对所有事物的承诺的函数。
使用 map()
函数通常也比使用 for
循环更简洁。
这是一个通用的食谱。假设您的 promise 实现具有某种 all
函数的风格:
var fetchOne = function(oneData){
//Use a library that returns a promise
return ajax.get("http://someurl.com/" + oneData);
};
var fetchAll = function(allData){
//map the data onto the promise-returning function to get an
//array of promises. You could also use `_.map` if you're a
//lodash or underscore user.
var allPromises = myData.map(fetchOne);
return Promise.all(allPromises);
};
var allData = ["a", "b", "c"];
var promiseForAll = fetchAll(allData);
//Handle the results for all of the promises.
promiseForAll.then(function(results){
console.log("All done.", results);
});
三期:
- 您没有 return 来自
getTagQuotes
方法的延迟承诺。 - 您正在查看
i
以查看您是否完成了循环,并且 for 循环甚至在第一次成功调用之前就已经完成 (i == (tags.length - 1)
)。 - 您在循环的第一次迭代中调用了
return
,以至于您甚至没有到达第二个项目。
这是更正后的代码(尚未测试)
function getTagQuotes(tags) {
var deferred = $q.defer(); // setting the defer
var url = 'app/api/social/twitter/volume/';
var tagsComplete = 0;
for (var i=0; i<tags.length; i++) {
rawTagData = [];
GetTweetVolFactory.returnTweetVol(url+tags[i].term_id)
.success(function(data, status, headers, config) {
rawTagData.push(data);
tagsComplete++;
if (tagsComplete === tags.length) {
formatTagData(rawTagData);
deferred.resolve();
}
});
}
return deferred.promise;
}
return deferred.promise;
应该是函数的 return 值,而不是 GetTweetVolFactory.returnTweetVol()
,因为那是你打算承诺的。
您的问题是您正在调用多个 GetTweetVolFactory.returnTweetVol()
,然后您需要合并所有这些异步调用以解决您的承诺。为此,您应该只承诺一个 GetTweetVolFactory.returnTweetVol()
调用:
function promisifiedTweetVol(rawTagData, urlStuff) {
var deferred = $q.defer(); // setting the defer
GetTweetVolFactory.returnTweetVol(urlStuff)
.success(function(data, status, headers, config) {
rawTagData.push(data);
// One the last loop, call formatTagData
// which fills the tweetArrayObjsContainer Array
if (loopStep === (rawTagData.length - 1)) {
formatTagData(rawTagData);
deferred.resolve();
}
});
return deferred.promise;
}
然后在循环中调用每个 promise 和 return 在所有 promise 完成时解析的 promise:
function getTagQuotes(tags) {
var url = 'app/api/social/twitter/volume/';
var promises = [];
// My for loop, which only returns ONCE, even if there are 3 tags
for (var i=0; i<tags.length; i++) {
var loopStep = if;
rawTagData = [];
promises.push( promisifiedTweetVol(rawTagData, url+tags[i].term_id) );
}
// ...
return $.when(promises);
}
您的代码还有一些问题,但您应该能够按照我的提示进行操作。
将每个承诺放入一个数组中,然后执行:
$q.all(arrayOfPromises).then(function(){
// this runs when every promise is resolved.
});
参考这个问题和
- 代码通常会在几个地方用 array.map() 代替
for
循环更清晰。 getTagQuotes()
将通过构建一系列承诺、将其提交给$q.all()
并返回聚合承诺来变得更简洁。formatTagData()
及其与调用者的关系将通过返回转换后的rawData
. 变得更清晰
根据一些假设,代码应简化为如下所示:
getTagQuotes(tags).then(function(tweetArrayObjsContainer) {
chartObj.chartData = chartObj.chartData.concat(tweetArrayObjsContainer); // concat() ...
// chartObj.chartData = tweetArrayObjsContainer; // ... or simply assign??
chartDirective = ScopeFactory.getScope('chart');
chartDirective.nvd3.drawChart(chartObj.chartData);
});
function getTagQuotes(tags) {
var url = 'app/api/social/twitter/volume/';
var promises = tags.map(function(tag) {
var deferred = $q.defer();
GetTweetVolFactory.returnTweetVol(url + tag.term_id)
.success(function(data, status, headers, config) {
deferred.resolve(data);
})
.error(function(data, status) {
console.log(tag.term_id + ': error in returning tweet data');
deferred.resolve(null); // resolve() here effectively catches the error
});
return deferred.promise;
});
return $q.all(promises).then(formatTagData); //this is much much cleaner than building an explicit data array and resolving an outer deferred.
function formatTagData(rawData) {
return rawData.filter(function(data) {
return data || false; // filter out any nulls
}).map(function(item, i) {
return {
'key': 'Quantity' + (i+1),
'type': 'area',
'yAxis': 1,
'color': tagColorArray[i],
'values': item.frequency_counts.reverse().map(function(c) {
return {
x: addZeroes(c.start_epoch),
y: c.tweets,
};
})
};
});
}
}