I have code that looks similar to this:
// Get the data via an AJAX call
fetchAjaxData()
.then(function(data) {
// Update or insert the data - cannot use a merge
data.forEach(function(item) {
updateTable(item)
.then(function(result)) {
// If no rows were updated, insert the item
if (result.rowsAffected == 0) {
insertIntoTable(item);
.then(function(result)) {
console.log("item added");
});
}
});
});
return data.length;
}).then(function(l) {
useInsertedItems(l);
});
The problem is when useInsertedItems runs, the data might not have yet been inserted or updated yet. How can I ensure that the data is fully updated or inserted before this point?
Promises signal completion via return values, so you should return your updateItem and insertIntoTable chain. Aggregating promises is done via Promise.all (or $q.all, Q.all, $.when etc depending on the library):
A corrected code would do these two:
fetchAjaxData()
.then(function(data) {
// Update or insert the data - cannot use a merge
// map is like `each` with return values for each item
var ps = data.map(function(item) {
return updateTable(item)
.then(function(result)) {
// If no rows were updated, insert the item
if (result.rowsAffected == 0) {
return insertIntoTable(item); // note the return
.then(function(result)) {
console.log("item added");
});
}
});
});
return Promise.all(ps).then(function(){ return data.length; });
}).then(function(l) {
useInsertedItems(l);
});
There is a great article which covers your case. At the same time, you can learn a lot of new stuff! http://taoofcode.net/promise-anti-patterns/
function workMyCollection(arr) {
return arr.reduce(function(promise, item) {
return promise.then(function(result) {
return doSomethingAsyncWithResult(item, result);
});
}, q());
}
Related
I have a number of promises and I want to execute them in order, but conditionally.
For example, I have the following promises:
getItems()
getItemsSource()
getItemsAlternativeSource()
What I want to do is try getItems() first. If this resolves with an empty value OR if it throws an error, I want to log that error (if that's the case), but then try getItemsSource(), same as above, if it resolves with no value or throws an error, I want to log the error if that's the case and try getItemsAlternativeSource().
I know I can do this conditionally, in each then() or catch(), but that seems a bit redundant. Is there a better way to handle this kind of control flow?
Thank!
You can use an empty value as the return value of the catch handler:
getItems().catch(function(err) {
console.warn(err);
return null; // <==
}).then(function(items) {
if (items) return items;
else return getItemsSource().catch(function(err) {
console.warn(err);
return null; // <==
}).then(function(sourceitems) {
if (items) return items;
else return getItemsAlternativeSource().catch(function(err) {
console.warn(err);
throw new Error("items couldn't be fetched normally, from source, or from alternative source");
});
});
});
If you absolutely want to avoid duplication, you can use this highly abstract approach:
var tryAll = [getItems, getItemsSource, getItemsAlternativeSource].reduceRight(function(nextAlternative, fetch) {
return function() {
return fetch().then(function(items) {
if (items) return items;
else return nextAlternative(); // now we can even call it in two locations
}, function(err) {
console.warn(err);
return nextAlternative(); // without having to resort to catch-then
});
};
}, function last() {
throw new Error("items couldn't be fetched normally, from source, or from alternative source");
});
tryAll();
I'd suggest you create a function that takes an array of functions that will call each function in the array until one returns with some data.
function getFirstData(array) {
var index = 0;
function next() {
if (index < array.length) {
return array[index++]().then(function(data) {
// if we got an answer, return it as the resolve value
if (data) return data;
// otherwise, reject so we go to the next one
return Promise.reject(null);
}).catch(function(err) {
if (err) console.err(err);
return next();
});
} else {
// got to the end of the array without a value
throw new Error("No data found");
}
}
return Promise.resolve().then(next);
}
var fns = [getItem, getItemsSource, getItemsAlternativeSource];
getFirstData(fns).then(function(data) {
// got data here
}).catch(function(err) {
// no data found here
});
If you want the functions to have arguments, then you can .bind() the arguments to the functions before putting them in the array.
And, here's a different implementation using .reduce() to traverse the array:
function getFirstData(array) {
return array.reduce(function(p, fn) {
return p.then(function(priorData) {
// if we already got some data, then just return it
// don't execute any more functions
if (priorData) return priorData;
return fn().catch(function(err) {
console.log(err);
return null;
});
});
}, Promise.resolve()).then(function(data) {
if (!data) {
throw new Error("No data found");
}
});
}
I have list of names separated by comma. What I want is I want to call server request for all names in a sequence and store result inside an array. I tried and it's working when I do have number of names which are there in string.
See Here - This is working when I know number of names
Now what I want is I want to make this code as generic. If I add one name in that string, It should handle automatically without adding any code for ajax request.
See Here - This is what I've tried. It's not working as expected.
shoppingList = shoppingList.split(",");
var result = [];
function fetchData(shoppingItem)
{
var s1 = $.ajax('/items/'+shoppingItem);
s1.then(function(res) {
result.push(new Item(res.label,res.price));
console.log("works fine");
});
if(shoppingList.length == 0)
{
completeCallback(result);
}
else
{
fetchData(shoppingList.splice(0,1)[0]);
}
}
fetchData(shoppingList.splice(0,1)[0]);
Problem
I am not getting how to detect that all promise object have been resolved so that I can call callback function.
To make the ajax requests in sequence, you have to put the recursive call in the callback:
function fetchList(shoppingList, completeCallback) {
var result = [];
function fetchData() {
if (shoppingList.length == 0) {
completeCallback(result);
} else {
$.ajax('/items/'+shoppingList.shift()).then(function(res) {
result.push(new Item(res.label,res.price));
console.log("works fine");
fetchData();
// ^^^^^^^^^^^
});
}
}
fetchData();
}
or you actually use promises and do
function fetchList(shoppingList) {
return shoppingList.reduce(function(resultPromise, shoppingItem) {
return resultPromise.then(function(result) {
return $.ajax('/items/'+shoppingItem).then(function(res) {
result.push(new Item(res.label,res.price));
return result;
});
});
}, $.when([]));
}
(updated jsfiddle)
Notice there is nothing in the requirements of the task about the ajax requests to be made sequentially. You could also let them run in parallel and wait for all of them to finish:
function fetchList(shoppingList) {
$.when.apply($, shoppingList.map(function(shoppingItem) {
return $.ajax('/items/'+shoppingItem).then(function(res) {
return new Item(res.label,res.price);
});
})).then(function() {
return Array.prototype.slice.call(arguments);
})
}
(updated jsfiddle)
// global:
var pendingRequests = 0;
// after each ajax request:
pendingRequests++;
// inside the callback:
if (--pendingRequest == 0) {
// all requests have completed
}
I have modified your code to minimal to make it work - Click here.
Please note your last assertion will fail as the item promise is not resolved in linear manner. Thus sequence of the item will change.
I want to send a list of new books to a user. So far the below code works fine. The problem is that I don't want to send a book multiple times, so I want to filter them.
Current code works fine:
function checkActiveBooks(books) {
var queue = _(books).map(function(book) {
var deferred = Q.defer();
// Get all alerts on given keywords
request('http://localhost:5000/books?l=0&q=' + book.name, function(error, response, body) {
if (error) {
deferred.reject(error);
}
var books = JSON.parse(body);
if (!_.isEmpty(books)) {
// Loop through users of current book.
var userBooks = _(book.users).map(function(user) {
// Save object for this user with name and deals.
return {
user: user,
book: book.name,
books: books
}
});
if (_.isEmpty(userBooks)) {
deferred.resolve(null);
} else {
deferred.resolve(userBooks);
}
} else {
deferred.resolve(null);
}
});
return deferred.promise;
});
return Q.all(queue);
}
But now I want to filter already sent books:
function checkActiveBooks(books) {
var queue = _(books).map(function(book) {
var deferred = Q.defer();
// Get all alerts on given keywords
request('http://localhost:5000/books?l=0&q=' + book.name, function(error, response, body) {
if (error) {
deferred.reject(error);
}
var books = JSON.parse(body);
if (!_.isEmpty(books)) {
// Loop through users of current book.
var userBooks = _(book.users).map(function(user) {
var defer = Q.defer();
var userBook = user.userBook.dataValues;
// Check per given UserBook which books are already sent to the user by mail
checkSentBooks(userBook).then(function(sentBooks) {
// Filter books which are already sent.
var leftBooks = _.reject(books, function(obj) {
return sentBooks.indexOf(obj.id) > -1;
});
// Save object for this user with name and deals.
var result = {
user: user,
book: book.name,
books: leftBooks
}
return deferred.resolve(result);
});
return Q.all(userBooks);
} else {
deferred.resolve(null);
}
});
return deferred.promise;
});
return Q.all(queue);
}
But above code doesn't work. It doesn't stop looping. I thought it made sense to use q.all twice, because it contains two loops. But I guess I'm doing it wrong...
First of all you should always promisify at the lowest level. You're complicating things here and have multiple deferreds. Generally you should only have deferreds when converting an API to promises. Promises chain and compose so let's do that :)
var request = Q.nfbind(require("request")); // a promised version.
This can make your code in the top section become:
function checkActiveBooks(books) {
return Q.all(books.map(function(book){
return request('http://.../books?l=0&q=' + book.name)
.get(1) // body
.then(JSON.parse) // parse body as json
.then(function(book){
if(_.isEmpty(book.users)) return null;
return book.users.map(function(user){
return {user: user, book: book.name, books: books };
});
});
});
}
Which is a lot more elegant in my opinion.
Now, if we want to filter them by a predicate we can do:
function checkActiveBooksThatWereNotSent(books) {
return checkActiveBooks(books).then(function(books){
return books.filter(function(book){
return checkSentBooks(book.book);
});
});
}
It's worth mentioning that the Bluebird library has utility methods for all this like Promise#filter and Promise#map that'd make this code shorter.
Note that if checkSentBook is asynchronous you'd need to modify the code slightly:
function checkActiveBooksThatWereNotSent(books) {
return checkActiveBooks(books).then(function(books){
return Q.all(books.map(function(book){ // note the Q.all
return Q.all([book, checkSentBooks(book.book)]);
})).then(function(results){
return results.filter(function(x){ return x[1]; })
.map(function(x){ return x[0]; });
});
});
}
Like I said, with different libraries this would look a lot nicer. Here is how the code would look like in Bluebird which is also two orders of magnitude faster and has good stack traces and detection of unhandled rejections. For fun and glory I threw in ES6 arrows and shorthand properties:
var request = Promise.promisify(require("request"));
var checkActiveBooks = (books) =>
Promise.
map(books, book => request("...&q=" + book.name).get(1)).
map(JSON.parse).
map(book => book.users.length ?
book.users.map(user => {user, books, book: book.name) : null))
var checkActiveBooksThatWereNotSent = (books) =>
checkActiveBooks(books).filter(checkBookSent)
Which I find a lot nicer.
Acting on #Benjamins's suggestion, here is what the code would look like when checkSentBooks returns a promise:
var request = Q.nfbind(require("request")); // a promised version.
function checkActiveBooks(books) {
return Q.all(_(books).map(function(book) {
// a callback with multiple arguments will resolve the promise with
// an array, so we use `spread` here
return request('http://localhost:5000/books?l=0&q=' + book.name).spread(function(response, body) {
var books = JSON.parse(body);
if (_.isEmpty(books)) return null;
return Q.all(_(book.users).map(function(user) {
return checkSentBooks(user.userBook.dataValues).then(function(sentBooks) {
// ^^^^^^ return a promise to the array for `Q.all`
return {
user: user,
book: book.name,
books: _.reject(books, function(obj) {
return sentBooks.indexOf(obj.id) > -1;
})
};
});
}));
});
}));
}
I have this function:
function doCalculateStopBefore(thisD, lastD){
thisD.attributes.start.fetch().then(function(){
return lastD.attributes.end.fetch();
}).then(function(){
// calculate some stuff here using thisD.attributes.start and lastD.attributes.stop
thisD.set('property', value); // <--- important. update thisD!
});
return thisD; // < --- !!! this line doesn't want for the promises chain!
}
thisD and lastD are Parse.Objects. I need to fetch this 2 fields (pointers to another parse Class), then calculate some stuff with this values and update thisD. THEN I want to finish the function...
the function is going to be called in a _.each loop like this:
_.each(myCollection.models,function(thisD,index){
if (index == 0){
// first entry of user
// do not do anything.
} else{
//thisD = doCalculateStopBefore(thisD,myCollection.models[index-1]);
// above is how I had it before.
// below is my implementation of Troy's reply:
doCalculateStopBefore(thisD,myCollection.models[index-1]).then(function(thisD) {
console.log(thisD.attributes);
thisDrive = thisD;
})
}
promises.push(thisD.save());
});
how can I put the return inside the last then, or chaining it somehow?
You need to return the Promise from your first fetch() in doCalculateStopBefore() and add a then() to it to capture the updated value. See the updated code below.
function doCalculateStopBefore(thisD, lastD){
return thisD.attributes.start.fetch().then(function(){
return lastD.attributes.end.fetch();
}).then(function(){
// calculate some stuff here using thisD.attributes.start
// and lastD.attributes.stop
thisD.set('property', value); // <--- important. update thisD!
return thisD;
});
}
You can call this in a loop as below using Parse.Promises.when() to coordinate all the saves.
var promises = [];
_.each(myCollection.models, function(thisD, index) {
if (index == 0) {
// first entry of user
// do not do anything.
} else {
promises.push(doCalculateStopBefore(thisD,myCollection.models[index-1]).then(function(thisD) {
console.log(thisD.attributes);
thisDrive = thisD;
return thisD.save();
}));
}
});
Parse.Promise.when(promises).then(function() {
console.log("done");
});
I have many calls to a service at the end of which i want to write to a file my final collection when all the callbacks of the service have returned.
is there there a way to be sure that all callbacks are done ?
for (id in idsCollection) {
object.callService(id, function (res) {
collection.push(res);
});
}
filewriter.writetoFile("filename.json", JSon.Stringify(collection));
EDIT : just for the record i'm using cheerio with nodeJS.
Create an array. Push something onto the array each time you set up a callback. Pop something off it each time the callback runs. Check to see if the array is empty inside the callback function. If it is empty, then all the callbacks are done.
I typically use the node-async library for this sort of thing. It makes it easy to do exactly what you're talking about:
async.each(yourArray,
function(element, next) {
// this callback gets called for each element in your array
element.doSomething(function(returnValue){
next(returnValue) // call next when you're done
}
}, function(err, returnValues) {
// when all the elements in the array are processed, this is called
if (err) return console.log(err);
console.log(returnValues) // this is an array of the returnValues
});
})
You could simply count them. In your case it seems you already know how many callbacks there are going to be.
var remaining = idsCollection.length; // assuming array
for (id in idsCollection) {
object.callService(id, function (res) {
collection.push(res);
remaining -= 1; // decrement by 1 per callback
// here you can check if remaining === 0 (all done)
});
}
you can use nimble lib http://caolan.github.io/nimble/.
nimble paralel example
var _ = require('nimble');
_.parallel([
function (callback) {
setTimeout(function () {
console.log('one');
callback();
}, 25);
},
function (callback) {
setTimeout(function () {
console.log('two');
callback();
}, 0);
}
], function(){
console.log('done')
});
output
> two
> one
> done
I see many answers here, but I hope that this solution may still help someone.
Create a promise for each callback to be extinguished as such:
function funcToLoop(arg){
return new Promise((resolve, reject) => {
try{
funcWithCallback(arg, (cbArg) => {
// do your stuff
resolve(cbArg)
});
} catch (e) {
reject(e)
}
});
}
Then, you can create a loop as a async function and handle eventual results/states/etc here:
async function mainLoop(array){
let results = [];
for (let arg of array){
results.push(await funcToLoop(arg))
}
// handle results
}
... or you can have a sync function, collect the promises and handle them:
function mainLoop(array){
let promises = [];
for (let arg of array){
promises.push(funcToLoop(arg))
}
Promise.all(promises).then(()=>{
// handle promises
})
}
Claudio
jQuery.Deferred() objects might be what you are looking for.
OR if you are using HTML5 you can use promises .
Here is how to create promises
var promise = new Promise(function(resolve, reject) {
// do a thing, possibly async, then…
if (/* everything turned out fine */) {
resolve("Stuff worked!");
}
else {
reject(Error("It broke"));
}
});
And here is how to use them
promise.then(function(result) {
console.log(result); // "Stuff worked!"
}, function(err) {
console.log(err); // Error: "It broke"
});
Check this link for more info
If you're using jQuery, you can use $.when
Example:
exmCall1 = $.getJson(..);
exmCall2 = $.getJson(..);
$.when(exmCall1, exmCall2).done(function (exmCall1Ret, exmCall2Ret) {
//do stuff
});
You can read the actual documentation here: http://api.jquery.com/jquery.when/
Or do some hardcode:
var running;
for (id in idsCollection) {
object.callService(id, function (res) {
collection.push(res);
running += 1;
});
}
var loop = setInterval(function() {
if(running >= idsCollection.length) {
filewriter.writetoFile("filename.json", JSon.Stringify(collection));
clearInterval(loop);
}
, 500);