I have a method which accepts function as a callback parameter. Both outer method and callback function returns promises which I collect to array, waiting for them to be resolved with Q.all.
function eval() {
var colReadPromisses = [];
config.mongodb.colls.forEach(function (collName) {
var outer = mongo.readCollection(collName,
function (jsonData) {
var prom = es.pushItemToES(jsonData, esIndexName, collName, i);
colReadPromisses.push(prom);
});
colReadPromisses.push(outer);
});
return Q.all(colReadPromisses);
}
Now, inner callback method is called many times and it takes some time to process all of them. While they are being processed, promises returned from 'readCollection' method gets 'undefined' causing 'Q.all(colReadPromisses);' to resolve.
So my two questions would be why nodejs loses track to the promises returned from 'readCollection' method and how do I avoid this?
Thanks for responses!
I'm assuming you want to get the results of all both readCollection and pushItemToES in the returned promise
function eval() {
return Q.all(config.mongodb.colls.map(function (collName) {
var pushPromisses = [mongo.readCollection(collName, function (jsonData) {
pushPromisses.push(es.pushItemToES(jsonData, esIndexName, collName, i));
})];
return pushPromisses[0] // once the outer promise resolves
.then(function() {
return Q.all(pushPromisses); // wait for them all (including the outer)
});
}));
}
The returned promise when resolved, will resolve to a two-dimension array
[0][0] will be the result of the mongo.readCollection(colls[0])
[0][1..n] will be the results of each pushItemToES from readCollection(colls[0])
[1][0] will be the result of themongo.readCollection(colls[1])`
[1][1..n] will be the results of each pushItemToES from readCollection(colls[1])
and so on
if you don't need the result of mongo.readCollection in the returned promise
function eval() {
return Q.all(config.mongodb.colls.map(function (collName) {
var pushPromisses = [];
return mongo.readCollection(collName, function (jsonData) {
pushPromisses.push(es.pushItemToES(jsonData, esIndexName, collName, i));
}).then(function() {
return Q.all(pushPromisses);
});
}));
}
in this case the returned promise is still two dimensional, but with just the results of the pushItemToES
[0][0..n] will be the results of each pushItemToES from readCollection(colls[0])
[1][0..n] will be the results of each pushItemToES from readCollection(colls[1])
I can't see any way around the two dimensional array in the resolved promise - because the es.pushItemToES is not called a "known" (at least not ahead of time) number of times
You must reorganize your code:
function eval() {
// replace forEach with map
var colReadPromisses = config.mongodb.colls.map(function (collName) {
// return pushItemToES promises
return new Promise(function(resolve) {
mongo.readCollection(collName,
function (jsonData) {
resolve(es.pushItemToES(jsonData, esIndexName, collName, i));
});
});
});
// colReadPromisses stores array of pushItemToES promises
return Q.all(colReadPromisses);
}
Related
var promiseReturningFuncs = [];
for(var i = 0; i < 5; i++){
promiseReturningFuncs.push(askQuestion);
}
var programmers = [];
Promise.reduce(promiseReturningFuncs, function(resp, x) {
console.log(typeof resp);
if(typeof resp != "function") {
programmers.push(resp);
}
return x();
})
.then(function(resp) {
programmers.push(resp);
console.log(programmers);
});
My goal: execute the askQuestion function in series and resolve an array of objects created by that function. (this function must execute in series so that it can respond to user input)
So imagine that the askQuestion function returns a promise that resolves a object I want to add to an array.
This is my messy way of doing it.
I am looking to find a cleaner way of doing it, ideally, i wouldn't even need to push to an array, I would just have a final .then, where the response is an array.
Since you appear to be using the Bluebird promise library, you have a number of built-in options for sequencing your promise returning functions. You can use Promise.reduce(), Promise.map() with a concurrency value of 1, Promise.mapSeries or Promise.each(). If the iterator function returns a promise, all of these will wait for the next iteration until that promise resolves. Which to use depends more upon the mechanics of how your data is structured and what result you want (neither of which you actually show or describe).
Let's suppose you have an array of promise returning functions and you want to call them one at a time, waiting for the one to resolve before calling the next one. If you want all the results, then I'd suggest Promise.mapSeries():
let arrayOfPromiseReturningFunctions = [...];
// call all the promise returning functions in the array, one at a time
// wait for one to resolve before calling the next
Promise.mapSeries(arrayOfPromiseReturningFunctions, function(fn) {
return fn();
}).then(function(results) {
// results is an array of resolved results from all the promises
}).catch(function(err) {
// process error here
});
Promise.reduce() could also be used, but it would accumulate a single result, passing it from one to the next and end with one final result (like Array.prototype.reduce() does).
Promise.map() is a more general version of Promise.mapSeries() that lets you control the concurrency number (the number of async operations in flight at the same time).
Promise.each() will also sequence your functions, but does not accumulate a result. It assumes you either don't have a result or you are accumulating the result out-of-band or via side effects. I tend to not like to use Promise.each() because I don't like side effect programming.
You could solve this in pure JS using ES6 (ES2015) features:
function processArray(arr, fn) {
return arr.reduce(
(p, v) => p.then((a) => fn(v).then(r => a.concat([r]))),
Promise.resolve([])
);
}
It applies the function given to the array in series and resolves to an array of the results.
Usage:
const numbers = [0, 4, 20, 100];
const multiplyBy3 = (x) => new Promise(res => res(x * 3));
// Prints [ 0, 12, 60, 300 ]
processArray(numbers, multiplyBy3).then(console.log);
You'll want to double check browser compatibility but this works on reasonably current Chrome (v59), NodeJS (v8.1.2) and probably most others.
You can use recursion so that you can move to the next iteration in a then block.
function promiseToExecuteAllInOrder(promiseReturningFunctions /* array of functions */) {
var resolvedValues = [];
return new Promise(function(resolve, reject) {
function executeNextFunction() {
var nextFunction = promiseReturningFunctions.pop();
if(nextFunction) {
nextFunction().then(function(result) {
resolvedValues.push(result);
executeNextFunction();
});
} else {
resolve(resolvedValues);
}
}
executeNextFunction();
}
}
Executing one after another using a recursive function( in a non promise way):
(function iterate(i,result,callback){
if( i>5 ) callback(result);askQuestion().then(res=>iterate(i+1,result.concat([res]),callback);
})(0,[],console.log);
For shure this can be wrapped in a promise:
function askFive(){
return new Promise(function(callback){
(function iterate(i,result){
if( i>5 ) callback(result);askQuestion().then(res=>iterate(i+1,result.concat([res]),callback);
})(0,[],console.log);
});
}
askFive().then(console.log);
Or:
function afteranother(i,promise){
return new Promise(function(resolve){
if(!i) return resolve([]);
afteranother(i-1,promise).then(val=>promise().then(val2=>resolve(val.concat([val2])));
});
}
afteranother(5,askQuestion).then(console.log);
I am experimenting some strange behaviours with jQuery promises on reject.
I have an array of promises, and I need to work with them when they all have been resolved/rejected.
To do so, I am using this:
var array_res = [];
array_res.push(promiseResolve('a'));
array_res.push(promiseReject('b'));
$.when.apply(null,array_res).always( function ( ) {
console.log(arguments);
//Work to do
} );
function promiseResolve (c) {
var promise = $.Deferred();
promise.resolve({a:c});
return promise;
}
function promiseReject (c) {
var promise = $.Deferred();
promise.reject({b:c});
return promise;
}
The issue is:
If I resolve both promises, everything works fine.
If I reject one of the promises, then the arguments come incomplete.
If I reject both of them, then the arguments come incomplete.
Here are 3 fiddles where you can check the behaviour:
https://jsfiddle.net/daepqzv1/1/
https://jsfiddle.net/daepqzv1/2/
https://jsfiddle.net/daepqzv1/3/
What I need is a way to get the arguments for both, rejected and resolved.
This is normal behavior for $.when(). If any promises you pass to $.when() reject, then $.when() will reject with the first reject reason that it finds. This is how it is coded.
This is similar to the way the ES6 Promise.all() works.
If you want all results, even if some promises reject, then you can use something like $.settle() or $.settleVal() that are defined in this code:
(function() {
function isPromise(p) {
return p && (typeof p === "object" || typeof p === "function") && typeof p.then === "function";
}
function wrapInPromise(p) {
if (!isPromise(p)) {
p = $.Deferred().resolve(p);
}
return p;
}
function PromiseInspection(fulfilled, val) {
return {
isFulfilled: function() {
return fulfilled;
}, isRejected: function() {
return !fulfilled;
}, isPending: function() {
// PromiseInspection objects created here are never pending
return false;
}, value: function() {
if (!fulfilled) {
throw new Error("Can't call .value() on a promise that is not fulfilled");
}
return val;
}, reason: function() {
if (fulfilled) {
throw new Error("Can't call .reason() on a promise that is fulfilled");
}
return val;
}
};
}
// pass either multiple promises as separate arguments or an array of promises
$.settle = function(p1) {
var args;
if (Array.isArray(p1)) {
args = p1;
} else {
args = Array.prototype.slice.call(arguments);
}
return $.when.apply($, args.map(function(p) {
// make sure p is a promise (it could be just a value)
p = wrapInPromise(p);
// Now we know for sure that p is a promise
// Make sure that the returned promise here is always resolved with a PromiseInspection object, never rejected
return p.then(function(val) {
return new PromiseInspection(true, val);
}, function(reason) {
// convert rejected promise into resolved promise by returning a resolved promised
// One could just return the promiseInspection object directly if jQuery was
// Promise spec compliant, but jQuery 1.x and 2.x are not so we have to take this extra step
return wrapInPromise(new PromiseInspection(false, reason));
});
})).then(function() {
// return an array of results which is just more convenient to work with
// than the separate arguments that $.when() would normally return
return Array.prototype.slice.call(arguments);
});
}
// simpler version that just converts any failed promises
// to a resolved value of what is passed in, so the caller can just skip
// any of those values in the returned values array
// Typically, the caller would pass in null or 0 or an empty object
$.settleVal = function(errorVal, p1) {
var args;
if (Array.isArray(p1)) {
args = p1;
} else {
args = Array.prototype.slice.call(arguments, 1);
}
return $.when.apply($, args.map(function(p) {
p = wrapInPromise(p);
return p.then(null, function(err) {
return wrapInPromise(errorVal);
});
}));
}
})();
$.settle() always resolves and it resolves with an array of PromiseInspection objects that you can then iterate over to see which promises resolved and which rejected and what the value or reason was.
$.settleVal() is a little simpler to iterate, but a little less generic because it doesn't give you the reject reason. It always resolves with an array where a reject will have a default value that you pass to it in the array in place of the resolved value.
FYI, both $.settle() and $.settleVal() can both be passed an array of promises like $.settle(arrayOfPromises) or multiple promise arguments as $.settle(p1, p2, p3) (as $.when() works). This saves having to use .apply() when you have an array of promises.
1 the value can not change in promise
for example
var t = function(s) {
var wait = function(dtd) {
var dtd = $.Deferred();
//new a Deferred object in function
var tasks = function() {
alert("complete!");
s = s + "hhh";
dtd.resolve(s); // change the state of deferred object
};
setTimeout(tasks, 5000);
// return promise object
return dtd.promise(s);
};
}
var s = "hhh";
$.when(t(s))
.then(function() {
alert(s);
}).then(function() {
alert(s);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
I can only got "hhh" instead of "hhhhhh"...
2
How to invoke promise chain with different values?like a.then(b(c)).then(f(d))
I put all values in a object and then pass it on chain...
The alert in your $.when is alerting the global variable not the resolve in tasks
Also , you never call wait() and tasks() doesn't return anything.
The return of the promise only returns to wait() which never gets called. Returning to the inner function does not return to the outer function
Also you have no arguments in then() to receive the resolved data.
In order to get data to the second then, you need to return something from the first one
var t = function (s) {
var wait = function () {
var dtd = $.Deferred();
//new a Deferred object in function
var tasks = function () {
alert("complete!");
s = s + "hhh";
dtd.resolve(s); // change the state of deferred object
};
setTimeout(tasks, 2000);
// return promise object
return dtd.promise();
};
// return the promise inside `wait`
return wait()
}
var s = "hhh";
$.when(t(s)).then(function (resolvedData) {
// return to the next then...just because we can
return resolvedData; // must return something if want to access it in next then
}).then(function(previousThenData) {
alert(previousThenData);// alert argument
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
Here's an contrived example of what's going on: http://jsfiddle.net/adamjford/YNGcm/20/
HTML:
Click me!
<div></div>
JavaScript:
function getSomeDeferredStuff() {
var deferreds = [];
var i = 1;
for (i = 1; i <= 10; i++) {
var count = i;
deferreds.push(
$.post('/echo/html/', {
html: "<p>Task #" + count + " complete.",
delay: count
}).success(function(data) {
$("div").append(data);
}));
}
return deferreds;
}
$(function() {
$("a").click(function() {
var deferreds = getSomeDeferredStuff();
$.when(deferreds).done(function() {
$("div").append("<p>All done!</p>");
});
});
});
I want "All done!" to appear after all of the deferred tasks have completed, but $.when() doesn't appear to know how to handle an array of Deferred objects. "All done!" is happening first because the array is not a Deferred object, so jQuery goes ahead and assumes it's just done.
I know one could pass the objects into the function like $.when(deferred1, deferred2, ..., deferredX) but it's unknown how many Deferred objects there will be at execution in the actual problem I'm trying to solve.
To pass an array of values to any function that normally expects them to be separate parameters, use Function.prototype.apply, so in this case you need:
$.when.apply($, my_array).then( ___ );
See http://jsfiddle.net/YNGcm/21/
In ES6, you can use the ... spread operator instead:
$.when(...my_array).then( ___ );
In either case, since it's unlikely that you'll known in advance how many formal parameters the .then handler will require, that handler would need to process the arguments array in order to retrieve the result of each promise.
The workarounds above (thanks!) don't properly address the problem of getting back the objects provided to the deferred's resolve() method because jQuery calls the done() and fail() callbacks with individual parameters, not an array. That means we have to use the arguments pseudo-array to get all the resolved/rejected objects returned by the array of deferreds, which is ugly:
$.when.apply($,deferreds).then(function() {
var objects = arguments; // The array of resolved objects as a pseudo-array
...
};
Since we passed in an array of deferreds, it would be nice to get back an array of results. It would also be nice to get back an actual array instead of a pseudo-array so we can use methods like Array.sort().
Here is a solution inspired by when.js's when.all() method that addresses these problems:
// Put somewhere in your scripting environment
if (typeof jQuery.when.all === 'undefined') {
jQuery.when.all = function (deferreds) {
return $.Deferred(function (def) {
$.when.apply(jQuery, deferreds).then(
// the calling function will receive an array of length N, where N is the number of
// deferred objects passed to when.all that succeeded. each element in that array will
// itself be an array of 3 objects, corresponding to the arguments passed to jqXHR.done:
// ( data, textStatus, jqXHR )
function () {
var arrayThis, arrayArguments;
if (Array.isArray(this)) {
arrayThis = this;
arrayArguments = arguments;
}
else {
arrayThis = [this];
arrayArguments = [arguments];
}
def.resolveWith(arrayThis, [Array.prototype.slice.call(arrayArguments)]);
},
// the calling function will receive an array of length N, where N is the number of
// deferred objects passed to when.all that failed. each element in that array will
// itself be an array of 3 objects, corresponding to the arguments passed to jqXHR.fail:
// ( jqXHR, textStatus, errorThrown )
function () {
var arrayThis, arrayArguments;
if (Array.isArray(this)) {
arrayThis = this;
arrayArguments = arguments;
}
else {
arrayThis = [this];
arrayArguments = [arguments];
}
def.rejectWith(arrayThis, [Array.prototype.slice.call(arrayArguments)]);
});
});
}
}
Now you can simply pass in an array of deferreds/promises and get back an array of resolved/rejected objects in your callback, like so:
$.when.all(deferreds).then(function(objects) {
console.log("Resolved objects:", objects);
});
You can apply the when method to your array:
var arr = [ /* Deferred objects */ ];
$.when.apply($, arr);
How do you work with an array of jQuery Deferreds?
When calling multiple parallel AJAX calls, you have two options for handling the respective responses.
Use Synchronous AJAX call/ one after another/ not recommended
Use Promises' array and $.when which accepts promises and its callback .done gets called when all the promises are return successfully with respective responses.
Example
function ajaxRequest(capitalCity) {
return $.ajax({
url: 'https://restcountries.eu/rest/v1/capital/'+capitalCity,
success: function(response) {
},
error: function(response) {
console.log("Error")
}
});
}
$(function(){
var capitalCities = ['Delhi', 'Beijing', 'Washington', 'Tokyo', 'London'];
$('#capitals').text(capitalCities);
function getCountryCapitals(){ //do multiple parallel ajax requests
var promises = [];
for(var i=0,l=capitalCities.length; i<l; i++){
var promise = ajaxRequest(capitalCities[i]);
promises.push(promise);
}
$.when.apply($, promises)
.done(fillCountryCapitals);
}
function fillCountryCapitals(){
var countries = [];
var responses = arguments;
for(i in responses){
console.dir(responses[i]);
countries.push(responses[i][0][0].nativeName)
}
$('#countries').text(countries);
}
getCountryCapitals()
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<h4>Capital Cities : </h4> <span id="capitals"></span>
<h4>Respective Country's Native Names : </h4> <span id="countries"></span>
</div>
As a simple alternative, that does not require $.when.apply or an array, you can use the following pattern to generate a single promise for multiple parallel promises:
promise = $.when(promise, anotherPromise);
e.g.
function GetSomeDeferredStuff() {
// Start with an empty resolved promise (or undefined does the same!)
var promise;
var i = 1;
for (i = 1; i <= 5; i++) {
var count = i;
promise = $.when(promise,
$.ajax({
type: "POST",
url: '/echo/html/',
data: {
html: "<p>Task #" + count + " complete.",
delay: count / 2
},
success: function (data) {
$("div").append(data);
}
}));
}
return promise;
}
$(function () {
$("a").click(function () {
var promise = GetSomeDeferredStuff();
promise.then(function () {
$("div").append("<p>All done!</p>");
});
});
});
Notes:
I figured this one out after seeing someone chain promises sequentially, using promise = promise.then(newpromise)
The downside is it creates extra promise objects behind the scenes and any parameters passed at the end are not very useful (as they are nested inside additional objects). For what you want though it is short and simple.
The upside is it requires no array or array management.
I want to propose other one with using $.each:
We may to declare ajax function like:
function ajaxFn(someData) {
this.someData = someData;
var that = this;
return function () {
var promise = $.Deferred();
$.ajax({
method: "POST",
url: "url",
data: that.someData,
success: function(data) {
promise.resolve(data);
},
error: function(data) {
promise.reject(data);
}
})
return promise;
}
}
Part of code where we creating array of functions with ajax to send:
var arrayOfFn = [];
for (var i = 0; i < someDataArray.length; i++) {
var ajaxFnForArray = new ajaxFn(someDataArray[i]);
arrayOfFn.push(ajaxFnForArray);
}
And calling functions with sending ajax:
$.when(
$.each(arrayOfFn, function(index, value) {
value.call()
})
).then(function() {
alert("Cheer!");
}
)
If you're transpiling and have access to ES6, you can use spread syntax which specifically applies each iterable item of an object as a discrete argument, just the way $.when() needs it.
$.when(...deferreds).done(() => {
// do stuff
});
MDN Link - Spread Syntax
I had a case very similar where I was posting in an each loop and then setting the html markup in some fields from numbers received from the ajax. I then needed to do a sum of the (now-updated) values of these fields and place in a total field.
Thus the problem was that I was trying to do a sum on all of the numbers but no data had arrived back yet from the async ajax calls. I needed to complete this functionality in a few functions to be able to reuse the code. My outer function awaits the data before I then go and do some stuff with the fully updated DOM.
// 1st
function Outer() {
var deferreds = GetAllData();
$.when.apply($, deferreds).done(function () {
// now you can do whatever you want with the updated page
});
}
// 2nd
function GetAllData() {
var deferreds = [];
$('.calculatedField').each(function (data) {
deferreds.push(GetIndividualData($(this)));
});
return deferreds;
}
// 3rd
function GetIndividualData(item) {
var def = new $.Deferred();
$.post('#Url.Action("GetData")', function (data) {
item.html(data.valueFromAjax);
def.resolve(data);
});
return def;
}
If you're using angularJS or some variant of the Q promise library, then you have a .all() method that solves this exact problem.
var savePromises = [];
angular.forEach(models, function(model){
savePromises.push(
model.saveToServer()
)
});
$q.all(savePromises).then(
function success(results){...},
function failed(results){...}
);
see the full API:
https://github.com/kriskowal/q/wiki/API-Reference#promiseall
https://docs.angularjs.org/api/ng/service/$q
I am kind of new to promises and are stuck on the following exercise.
I have an array of values and I want to execute an async call on each one.
In the callback, I want to execute another call on the outcome of the first call.
Basically, my frustration is in the following:
The order of execution should be '1x2x3x' but the order is '123xxx'
In other words, the loop is already going to the next iteration when the sub/nested promise of the first promise is not fullfilled yet..
var values = ["1", "2", "3"];
function do(val) {
var deferred = Q.defer();
asyncCall(val)
.then( function( response ) {
console.log(val);
asyncCall(response)
.then( function ( response ) {
console.log('x');
deferred.resolve(true)
});
});
return deferred.promise;
}
var result = do(values[0]);
values.forEach( function(f) {
result = result.then(do(f));
}
There is probably an easy solution but I'm stuck on it.
You don't need the deferred, that's the deferred anti pattern you have there since promises chain.
Also, you have to return a promise from a .then handler if you want it to wait for it to resolve.
You can simply use a for loop:
function do(val) {
var q = Q();
for(var i = 0; i < val; i++){
q = q.then(asyncCall.bind(null,i))
.then(console.log.bind(console))
.then(console.log.bind(console,"x"));
}
return q; // in case you want to chain
}
fiddle.
Note: Bind just fixates the value for the function call. In this case since the first param (the this value) is null it acts like function(fn,arg){ return function(arg){ return fn(arg); }} that is, it translates a function call to a "partial application" - for more info see the MDN docs.