Series control flow with bluebird promises - javascript

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");
}
});
}

Related

Using Promises to defer continuation within a forEach loop

My goal with the below is to:
Capture a list of resolutions
Scan through each of them (in order) to find the first one that results in a successful stream
To test this, I have testVideoPresence:
var testCounter = 0;
function testVideoPresence(videoElement) {
testCounter++;
if (testCounter >= 5) {
testCounter = 0;
return false;
}
if (!videoElement.videoWidth || videoElement.videoWidth < 10) { // check to prevent 2x2 issue
setTimeout(function() {
testVideoPresence(videoElement); // try again
}, 500);
} else if (video.videoWidth * video.videoHeight > 0) {
return true;
}
}
As you can see, I'm using a setTimeout to recurse at most 5 times. This is where things get tricky:
resolutionTestBuilder.buildTests().then(function (resolutionTests) {
// at this point, I have a set of resolutions that I want to try
resolutionTests.forEach(function (resolutionTest) {
// then I want to iterate over all of them until I find one that works
performTest(resolutionTest).then(function (result) {
video.srcObject = result.mediaStream; // start streaming to dom
if (testVideoPresence(video)) { // here is the pain point - how do I await the result of this within the forEach?
// return the dimensions
} else {
// continue scanning
}
}).catch(function (error) {
logger.internalLog(error);
});
// wait to continue until we have our result
});
}).catch(function (error) {
logger.internalLog(error);
});
function performTest(currentTest) {
return streamHelper.openStream(currentTest.device, currentTest.resolution).then(function(streamData) {
return streamData;
}).catch(function (error) {
logger.internalLog(error);
});;
};
streamHelper.openStream = function (device, resolution) {
var constraints = createVideoConstraints(device, resolution);
logger.internalLog("openStream:" + resolution.label + ": " + resolution.width + "x" + resolution.height);
return navigator.mediaDevices.getUserMedia(constraints)
.then(function (mediaStream) {
streamHelper.activeStream = mediaStream;
return { stream: mediaStream, resolution: resolution, constraints: constraints };
// video.srcObject = mediaStream; // push mediaStream into target element. This triggers doScan.
})
.catch(function (error) {
if (error.name == "NotAllowedError") {
return error.name;
} else {
return error;
}
});
};
I'm trying to wait for the result within the forEach before continuing through the array of resolutions. I know I can use some advanced techniques like async/await if I want to transpile - but I'm stuck with vanilla JS and promises / bluebird.js for now. What are my options? Disclaimer - I am new to promises so the above code could be very malformed.
Update:
Tests are defined in order of importance - so I do need resolutionTests[0] to resolve before resolutionTests[1].
If the order of trials isn't important, you can simply use a map combined with Promise.race to make sure the first promise of a list that resolves resolves the whole list. You also need to make sure your promises return other promises inside then.
resolutionTestBuilder.buildTests().then(function (resolutionTests) {
return Promise.race(resolutionTests.map(function (resolutionTest) {
return performTest(resolutionTest).then(function (result) {
video.srcObject = result.mediaStream; // start streaming to dom
return testVideoPresence(video);
}).catch(function (error) {
logger.internalLog(error);
});
}));
}).catch(function (error) {
logger.internalLog(error);
});
This of course assumes that testVideoPresence does NOT resolve when you the dimensions are not available.
If the order of trial is important then a reduce approach might work.
This will basically result in a sequential application of the promises and the resulting promise will until all of them are resolved.
However, once the solution is found we attach it to the collector of the reduce so that further trials simply return that as well and avoid further tests (because by the time this is found the chain is already registered)
return resolutionTests.reduce(function(result, resolutionTest) {
var nextPromise = result.intermPromise.then(function() {
if (result.found) { // result will contain found whenver the first promise that resolves finds this
return Promise.resolve(result.found); // this simply makes sure that the promises registered after a result found will return it as well
} else {
return performTest(resolutionTest).then(function (result) {
video.srcObject = result.mediaStream; // start streaming to dom
return testVideoPresence(video).then(function(something) {
result.found = something;
return result.found;
});
}).catch(function (error) {
logger.internalLog(error);
});
}
);
return { intermPromise: nextPromise, found: result.found };
}, { intermPromise: Promise.resolve() }); // start reduce with a result with no 'found' and a unit Promise
At first , your testVideoPresence returns undefined. It wont work that way. May do:
function testVideoPresence(videoElement,callback,counter=0) {
if(counter>10) callback(false);
if (!videoElement.videoWidth || videoElement.videoWidth < 10) {
setTimeout(testVideoPresence, 500,videoElement,callback,counter+1);
} else if (video.videoWidth * video.videoHeight > 0) {
callback(true);
}
}
SO you can do:
testVideoPresence(el, console.log);
Now to the forEach. You cannot yield the forEach in any way. However you could write your own recursive forEach:
(function forEach(el,index) {
if(index>=el.length) return false;
performTest(el[index]).then(function (result) {
video.srcObject = result.mediaStream; // start streaming to dom
testVideoPresence(video,function(success){
if(!success) return alert("NOO!");
//do sth
//proceed
setTimeout(forEach,0,el,index+1);
});
}).catch(function (error) {
logger.internalLog(error);
});
})(resolutionTests,0);//start with our element at index 0
function raceSequential(fns) {
if(!fns.length) {
return Promise.resolve();
}
return fns.slice(1)
.reduce(function(p, c) {
return p.catch(c);
}, fns[0]());
}
// "Resolution tests"
var t1 = function() { return new Promise(function(_, reject) { setTimeout(() => reject('t1'), 1000); })};
var t2 = function() { return new Promise(function(resolve) { setTimeout(() => resolve('t2'), 1000); })};
var t3 = function() { return new Promise(function(resolve) { setTimeout(() => resolve('t3'), 1000); })};
var prom = raceSequential([t1, t2, t3])
.then(function(result) { console.log('first successful result: ' + result); });
Scanning your code indicates you have other async-related problems.

getting object Parse from a loop

In a Parse server function, it's getting Matches and profiles.
From a query to get matches another function is called to get Profiles by id but the result is :
{"_resolved":false,"_rejected":false,"_reso resolvedCallbacks":[],"_rejectedCallbacks":[]}
Main Query :
mainQuery.find().then(function(matches) {
_.each(matches, function(match) {
// Clear the current users profile, no need to return that over the network, and clean the Profile
if(match.get('uid1') === user.id) {
match.set('profile2', _processProfile(match.get('profile2')))
match.unset('profile1')
}
else if (match.get('uid2') === user.id) {
var profileMatch = _getProfile(match.get('profile1').id);
alert(">>>"+JSON.stringify(profileMatch));
match.set('profile1', _processProfile(match.get('profile1')))
match.unset('profile2')
}
})
the function to get Profile info:
function _getProfile(id){
var promise = new Parse.Promise();
Parse.Cloud.useMasterKey();
var queryProfile = new Parse.Query(Profile);
return queryProfile.equalTo("objectId",id).find()
.then(function(result){
if(result){
promise.resolve(result);
alert("!!!!"+result);
}
else {
console.log("Profile ID: " + id + " was not found");
promise.resolve(null);
}
},
function(error){
promise.reject(error)
});
return promise;
}
Just found this a little late. You've probably moved on, but for future readers: the key to solving something like this is to use promises as returns from small, logical asynch (or sometimes asynch, as in your case) operations.
The whole _getProfile function can be restated as:
function _getProfile(id){
Parse.Cloud.useMasterKey();
var queryProfile = new Parse.Query(Profile);
return queryProfile.get(id);
}
Since it returns a promise, though, you cannot call it like this:
var myProfileObject = _getProfile("abc123");
// use result here
Instead, call it like this:
_getProfile("abc123").then(function(myProfileObject) { // use result here });
Knowing that, we need to rework the loop that calls this function. The key idea is that, since the loop sometimes produces promises, we'll need to let those promises resolve at the end.
// return a promise to change all of the passed matches' profile attributes
function updateMatchesProfiles(matches) {
// setup mainQuery
mainQuery.find().then(function(matches) {
var promises = _.map(matches, function(match) {
// Clear the current users profile, no need to return that over the network, and clean the Profile
if(match.get('uid1') === user.id) {
match.set('profile2', _processProfile(match.get('profile2'))); // assuming _processProfile is a synchronous function!!
match.unset('profile1');
return match;
} else if (match.get('uid2') === user.id) {
var profileId = match.get('profile1').id;
return _getProfile(profileId).then(function(profileMatch) {
alert(">>>"+JSON.stringify(profileMatch));
match.set('profile1', _processProfile(match.get('profile1')))
match.unset('profile2');
return match;
});
}
});
// return a promise that is fulfilled when all of the loop promises have been
return Parse.Promise.when(promises);
}

Awaiting nested asynchronous tasks using Promises

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());
}

Multiple Q.all inside function?

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;
})
};
});
}));
});
}));
}

a way to know when all callbacks are done in javascript

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);

Categories

Resources