Chaining multiple promises (handling callbacks) - javascript

I am having some difficulties with promises when it comes chaining multiple ones. The confusion is distinguishing how to properly take advantage of promises & their difference with Callbacks. I noticed that callbacks sometime fire regardless a promise is resolved or not, making the below implementation unreliable..(Unless my syntax & logic are wrong) I read the official documentation and came up with this, but I am not sure it is well implemented.The Registration flow is as follow:
User chooses an Alias -> Details Alias + userID (Device's Universally Unique Identifier) are sent server side
If Alias is available, ApiKey(token) is generated, User registered and sent back client side (Stored in DB)
Services.js
(function(angular) {
myApp.factory("deviceDB.Service", ['$resource', '$http', '$q',
function ($resource, $http , $q ) {
return {
//Second Promsie : After API token is generated server-side, store res in db
RegDevice: function (alias, apiKey, userID) {
var deferred = $q.defer();
var configuration ;
var db = window.sqlitePlugin.openDatabase({name: "config.db"});
setTimeout(function () {
db.transaction(function (tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS user_details (userID UNIQUE , alias TEXT, apiKey TEXT)');
tx.executeSql("INSERT INTO user_details (userID, alias, apiKey) VALUES (?,?,?)", [userID, alias, apiKey], function (tx, res) {
deferred.resolve(configuration = true);
}, function (e) {
// console.log("ERROR: " + e.message);
deferred.reject(configuration = false);
});
});
}, 1000);
return deferred.promise;
},
//First Promsie: Register user server side & generate APi token
RegUser: function (alias, userID) {
var deferred = $q.defer();
var pro;
pro = $resource('api/query/register', {'alias': alias, 'userID': userID},
{ query: {
isArray: false,
method: 'GET' } });
setTimeout(function () {
pro.query(function (res) {
if (res.error) {
deferred.reject( { error : res.error, exists: res.exists, msg: res.message } );
}
else {
deferred.resolve( {error : res.error , alias: res.alias , apiKey: res.apiKey, msg: res.message } );
}
}, function (e) {
deferred.reject( { errorStatus: e.status } );
});
}, 1000);
return deferred.promise;
}
};
}]);
}(window.angular));
Now, in My controller I would like to chain both promises above. I quote the follwoing from the Documentation :
then(successCallback, errorCallback, notifyCallback) – regardless of when the promise was or will be resolved or rejected, then calls one of the success or error callbacks asynchronously as soon as the result is available. The callbacks are called with a single argument: the result or rejection reason. Additionally, the notify callback may be called zero or more times to provide a progress indication, before the promise is resolved or rejected.
What is the point of having Callbacks if they can fire regardless if the Promise is resolved?
Shouldn't I call for e.g Promise2 within the first Promise's Success Callback? If it is fired regardless of of Promise1 being resolved, How then can I chain Promise2 in a way to fire only when Promise1 is resolved?
What I tried :
Controller.js
myApp.controller('RegisterController', ['$scope', '$http', 'deviceDB.Service',
function ($scope , $http , deviceDB.Service) {
var Promise1 = deviceDB.RegUser($scope.alias, $scope.Device);
// First promise - Validate with server
Promise1.then(function(data)
{
console.log(' Registration Server-Side successfully');
$scope.apiKey = data.apiKey;
term.echo(data.apiKey);
}, function(e)
{
console.log('Registration Failed');
term.echo(e.msg);
})
//Call Promise 2 & Store details Client-Side using .then()
.then(deviceDB.RegDevice($scope.alias, $scope.apiKey, $scope.Device),
function(d){
console.log('Items Stored in DB successfully');
}, function()
{
console.log('Items Stored in DB Failed');
});
}]);
Notes: I understand it is a bad practice to store details client-side, however, i am after a different concept (anonymous messaging) and there is no security concerns..
Thanks for your time

Your second then call seems incorrect, after
//Call Promise 2 & Store details Client-Side using .then()
then takes up-to 3 parameters then(successCallback, errorCallback, notifyCallback) you are passing it: deviceDB.RegDevice($scope.alias, $scope.apiKey, $scope.Device) which is evaluated immediately and the promise returned is passed to the function then as the success function, your success function is passed as the errorCallback and your fail function is passed as the notifyCallback.
I would try the following
Promise1.then(function(data)
{
console.log(' Registration Server-Side successfully');
$scope.apiKey = data.apiKey;
term.echo(data.apiKey);
return deviceDB.RegDevice($scope.alias, $scope.apiKey, $scope.Device)
}, function(e)
{
console.log('Registration Failed');
term.echo(e.msg);
return e;
}).then(function(d) {/*all good*/}, function(e) {/* all bad */}
Notice the call to RegDevice is now within a function block, and a promise is returned from the then block you want to chain from.

I find $q.serial a great library for chaining promises. It's very easy to use and handles a lot of stuff like checking if all promises on the chain are really promises.
Here is a small example:
function do_all() {
var task_1 = function() {
return $http.get("some url")
.then(on_xhr_completed_fn, on_xhr_failed_fn);
}
var task_2 = function(some_data) {
vm.bla = some_data;
return $http.get("other url")
.then(on_xhr_completed_fn, on_xhr_failed_fn);
}
var task_3 = function(other_data) {
vm.bli = other_data;
}
var tasks = [task_1, task_2, task_3];
return $q.serial(tasks)
.then(function() {
console.log("Finished tasks 1, 2 and 3!!!");
});
}

Here's an approach that may be helpful using async/await:
async function run_promise_A(args) {
return new Promise((resolve, reject) => {
return resolve(resolve_value)
});
}
async function run_promise_B(args) {
return new Promise((resolve, reject) => {
return resolve(resolve_value)
});
}
async function run_promise_C(args) {
return new Promise((resolve, reject) => {
return resolve(resolve_value)
});
}
async function run_several_async_functions(userid) {
let a = run_promise_A(userid);
let b = run_promise_B(a);
let c = run_promise_C(b);
return c;
}
return Promise.resolve()
.then(() => {
let c = (async () => {
let c = await run_several_async_functions(userid)
return c;
})();
return c;
})
.then((c) => {
return c;
})
.catch((err) => {
console.log(err);
});

Related

Is the JavaScript promise handler determined by the order of its functions?

exports.userDidSignOut = functions.https.onCall((data) => {
const userId = data.userId;
const settingsUpdate = {
"fcmToken": null,
};
const promise = admin.firestore().collection("user-settings").doc(userId).update(settingsUpdate);
promise.then(
(_value) => {
return null;
},
(reason) => {
return console.log(reason);
}
);
});
I'm working with a database (Firestore) that returns a promise when data is updated. In this snippet above, I update the database with update() and it returns a value parameter on success and a reason parameter on failure. Because I can rename these parameters freely, how does the promise know which function is the success and which is the error? Is it purely based on the order they appear? And can I remove the value parameter entirely and will the promise know that's the function to call on success?
promise.then(
() => {
return null; // is removing the parameter of this function safe to do?
},
(reason) => {
return console.log(reason);
}
);

Why isn't my async function waiting for the promise to be fulfilled

I am using ldapjs to query users from an ldap server.
If I put all the code just in a single script without using functions, the query works and I get the results I need.
I am now trying to use expressjs to serve a rest endpoint to enable querying of the ldap server, so I moved the ldapjs client.search code into a async function with a promise surrounding the actual search code.
After the promise code, I have a line which exercises the promise using await and stores the results of the promise in a variable. I then return that variable to the calling function which will eventually send the results back as a json-formatted string to the requesting browser.
The problem I am seeing is that the console.log() of the returned results is undefined and appears before the console.log statements inside the promise code. So it looks like the async function is returning before the promise is fulfilled, but I don't see why because in all the examples of promises and async/await I have seen this scenario works correctly.
Below is a sample script without the expressjs part to just make sure everything works correctly.
// script constants:
const ldap = require('ldapjs');
const assert = require('assert');
const ldapServer = "ldap.example.com";
const adSuffix = "dc=example,dc=com"; // test.com
const client = getClient();
const fullName = "*doe*";
var opts = {
scope: "sub",
filter: `(cn=${fullName})`,
attributes: ["displayName", "mail", "title", "manager"]
};
console.log("performing the search");
let ldapUsers = doSearch(client, opts);
console.log("Final Results: " + ldapUsers);
function getClient() {
// Setup the connection to the ldap server
...
return client;
}
async function doSearch(client, searchOptions) {
console.log("Inside doSearch()");
let promise = new Promise((resolve, reject) => {
users = '{"users": [';
client.search(adSuffix, searchOptions, (err, res) => {
if (err) {
console.log(err);
reject(err)
}
res.on('searchEntry', function(entry) {
console.log("Entry: " + users.length);
if (users.length > 11) {
users = users + "," + JSON.stringify(entry.object);
} else {
users = users + JSON.stringify(entry.object);
}
});
res.on('error', function(err) {
console.error("Error: " + err.message);
reject(err)
});
res.on('end', function(result) {
console.log("end:");
client.unbind();
users = users + "]}";
resolve(users)
});
});
});
// resolve the promise:
let result = await promise;
console.log("After promise has resolved.");
console.log(result);
return result
}
The output from the console.log statements is as follows:
Setting up the ldap client.
ldap.createClient succeeded.
performing the search
Inside doSearch()
Final Results: [object Promise]
Entry: 11
end:
After promise has resolved.
{"users": [{"dn":"cn=john_doe"}]}
I did strip out the code which creates the ldapjs client and redacted the company name, but otherwise this is my code.
Any ideas on why the doSearch function is returning before the promise is fulfilled would be greatly appreciated.
As #danh mentioned in a comment, you're not awaiting the response from doSearch. Since doSearch is an async function it will always return a promise, and thus must be awaited.
As a quick and dirty way to do that you could wrap your call in an immediately invoked asynchronous function like so:
// ...
(async () => console.log(await doSearch(client, opts)))();
// ...
For more info you might check out the MDN docs on asynchronous functions
I think there are a few issues in the provided code snippet. As #danh pointed out you need to await the doSearch call. However you may have not done that already since you may not be using an environment with a top async. This likely means you'll want to wrap the call to doSearch in an async function and call that. Assuming you need to await for the search results.
// script constants:
const ldap = require('ldapjs');
const assert = require('assert');
const ldapServer = "ldap.example.com";
const adSuffix = "dc=example,dc=com"; // test.com
const client = getClient();
const fullName = "*doe*";
function getClient() {
// Setup the connection to the ldap server
...
return client;
}
async function doSearch(client, searchOptions) {
console.log("Inside doSearch()");
return new Promise((resolve, reject) => {
users = '{"users": [';
client.search(adSuffix, searchOptions, (err, res) => {
if (err) {
console.log(err);
reject(err)
}
res.on('searchEntry', function(entry) {
console.log("Entry: " + users.length);
if (users.length > 11) {
users = users + "," + JSON.stringify(entry.object);
} else {
users = users + JSON.stringify(entry.object);
}
});
res.on('error', function(err) {
console.error("Error: " + err.message);
reject(err)
});
res.on('end', function(result) {
console.log("end:");
client.unbind();
users = users + "]}";
console.log(result);
resolve(users)
});
});
});
}
const opts = {
scope: "sub",
filter: `(cn=${fullName})`,
attributes: ["displayName", "mail", "title", "manager"]
};
(async function runAsyncSearch () {
console.log("performing the search");
try {
const ldapUsers = await doSearch(client, opts); // Await the async results
console.log("After promise has resolved.");
console.log("Final Results: " + ldapUsers);
} catch (err) {
console.error(err.message);
}
})(); // Execute the function immediately after defining it.

Returning one object that's built with nested promises

I'm struggling to wrap my head around a nested promise layout where one one object is returned at the end of it. My current code is as follows:
router
router.get(`/${config.version}/event/:id?`, function (req, res, next) {
var event = new Event(req, res, next);
event.getInfo(req.params.id).then((info) => {
res.send(info);
});
});
function
getInfo(id) {
db.main('events').where('id', id).select()
.then((result) => {
if(result.length > 0) {
var event = result[0];
//regular functions
event.status = this.getStatus(id);
event.content = this.getContent(id);
event.price = this.getPrice(id);
//promise functions
var users = this.getUsers(id);
var hosts = this.getHosts(id);
Promise.all([users, hosts]).then(values => {
event.users = values[0];
event.hosts = values[1];
//return whole event object to router
return event;
})
.catch((err) => {
return {
result: 'error',
error: err
};
});
} else {
return {
result: 'error',
error: "Event does not exist"
};
}
}).catch((e) => {
return {
result: 'error',
error: "Could not retrieve event info"
};
});
}
As you can see, the router initiates a call to get info about an event. The function then does a database call and gets some event data. Thereafter I need to get the users and hosts of the event from a different table, append that info to the event object as well and then return the whole object to the router to be sent to the client.
When I do this I get an error because I'm not returning a promise from the getInfo function, but I'm not sure how or which promise I'm supposed to return.
I'd appreciate some help with this. Thanks
using .then means that you are returning a promise.
function getInfo(id) {
return new Promise(function(resolve, reject) {
resolve('yay!');
})
}
getInfo().then(function(result) { //result = yay! });
to make your code work, simply replace all the returns with resolves, the errors with rejects, and wrap the whole thing with a return new Promise as i did.
getInfo(id) {
return new Promise(function(resolve, reject) {
db.main('events').where('id', id).select()
.then((result) => {
if (result.length > 0) {
var event = result[0];
//regular functions
event.status = this.getStatus(id);
event.content = this.getContent(id);
event.price = this.getPrice(id);
//promise functions
var users = this.getUsers(id);
var hosts = this.getHosts(id);
Promise.all([users, hosts]).then(values => {
event.users = values[0];
event.hosts = values[1];
//return whole event object to router
resolve(event);
})
.catch((err) => {
reject({
result: 'error',
error: err
});
});
} else {
reject({
result: 'error',
error: "Event does not exist"
});
}
}).catch((e) => {
reject({
result: 'error',
error: "Could not retrieve event info"
});
});
});
}
Just wrap your async code in Promise like this:
getInfo(id) {
return new Promise(function(resolve, reject) {
db.main('events').where('id', id).select()
.then((result) => {
//...
resolve(/* result */)
// OR
reject(/* Error */)
})
}
Note: Use resolve and reject instead return
It's a combination of several things, but the main one is that you are never returning anything from getInfo, so your router handler is calling .then on undefined.
Do not call .catch (without throwing inside it) on Promises you intend to return for a caller to consume. This makes it not possible to use .catch, because you recovered the Promise chain into a resolved one.
Whatever you return inside a .then will be merged into the promise chain, so it's not actually a "Promise that resolves with a Promise". Your whole code could be replaced with:
getInfo (id) {
return db.main('events').where('id', id).select()
.then(result => {
if (result.length == 0) {
// you can also just throw your error object thing,
// but standard Error are generally the convention
throw new Error('Event does not exist')
}
const [event] = result
event.status = this.getStatus(id)
event.content = this.getContent(id)
event.price = this.getPrice(id)
return Promise.all([this.getUsers(id), this.getHosts(id)])
.then(([users, hosts]) => {
event.users = users
event.hosts = hosts
// this is the only value that
// this.getInfo(id).then(value => {/* ... */}) will see
return event
}
})
}

ES6 promises, chaining promises only if previous promises were rejected, while keeping rejection reasons

I'm building an authentication API that tries to authenticate against different authentication methods until one succeeds (like Spring Security).
Each authentication method returns a promise, fulfilled if the authentication did succeed, rejected if the authentication did fail.
To try to authenticate a user, I need call the authentication methods sequentially:
If the first authentication method succeeds the user is authenticated and I
don't want the other authentication methods to be called.
If the first authentication method fails, I need to try the second authentication method. And so on...
If none of the authentication methods succeed, I need to reject the authentication and maybe to
know the reasons every authentication did fail.
If an authentication succeeds I need to get back a value (authentication object).
This process is almost what promises libraries call .any (or .some) except that I don't want to execute every promises (hence try every authentication methods, which induces unnecessary workload). I want to try the next authentication method only if the previous failed.
Question 1: Is there a function available in a Promise/A+ compliant library that already does that ?
Question 2: I've been thinking of the following way (cf code below), is there any better way ?
// Defining some promise factories
var promiseFactory1 = {
buildPromise: function() {
return new Promise((resolve, reject) => {
console.log('trying p1...');
// resolve('p1 success');
reject('p1 failure');
})
}
};
var promiseFactory2 = {
buildPromise: function() {
return new Promise((resolve, reject) => {
console.log('trying p2...');
// resolve('p2 success');
reject('p2 failure');
})
}
};
var promiseFactory3 = {
buildPromise: function() {
return new Promise((resolve, reject) => {
console.log('trying p3...');
resolve('p3 success');
// reject('p3 failure');
})
}
};
// Building the promise
let promisesFactory = [promiseFactory1, promiseFactory2, promiseFactory3];
let rejections = [];
var reducedPromise = promisesFactory.reduce(function(promise, nextFactory) {
if (promise === null) return nextFactory.buildPromise();
return promise.catch(err => {
rejections.push(err);
return nextFactory.buildPromise();
});
}, null);
reducedPromise
.catch(err => {
rejections.push(err); // catching the last rejection
})
.then(success => {
if(rejections.length == promisesFactory.length) {
console.log(rejections);
// TODO return Promise.reject(new SomeCustomError());
} else {
console.log(success);
// TODO return Promise.resolve(success);
}
});
I believe you can do it like this (where auth1, auth2, and auth3 are the auth functions returning promises):
auth1(/*args*/)
.catch(failed => auth2(/*args*/))
.catch(failed => auth3(/*args*/))
.then(result => {
console.log("Success:" + result);
return result;
})
.catch(failed => {
console.log("Failure:" + failed);
});
Because we don't have then callbacks, the resolution value propagates to the final then (just like having .then(result => result)); but we return new promises from the catch callbacks, triggering the next authentication method.
The then at the end gets the resolution value of the first succeeding auth method; the final catch above just gets the rejection reason of the last failing auth method (auth3).
Live on Babel's REPL
If you need all the failing reasons, you could keep them in an array:
let failures = [];
auth1(/*args*/)
.catch(failed => {failures.push(failed); return auth2(/*args*/);})
.catch(failed => {failures.push(failed); return auth3(/*args*/);})
.then(result => {
console.log("Success: " + result + " (failed: " + failures.join(", ") + ")");
return result;
})
.catch(failed => {
failures.push(failed);
console.log("Failures: " + failures.join(", "));
});
Live Copy
If your auth methods are themselves in an array, you can do the above in a loop:
let auths = [auth1.bind(null, /*args*/), auth2.bind(null, /*args*/), auth3.bind(null, /*args*/)];
return auths.reduce((p, auth) => p.catch(failed => auth()), Promise.reject())
.then(result => {
console.log("Success: " + result);
return result;
})
.catch(failed => {
console.log("Failed: " + failed);
});
Live Copy

Return Meteor.http results in method

I have a Meteor method that wraps around an http.get. I am trying to return the results from that http.get into the method's return so that I can use the results when I call the method.
I can't make it work though.
Here's my code:
(In shared folder)
Meteor.methods({
getWeather: function(zip) {
console.log('getting weather');
var credentials = {
client_id: "string",
client_secret: "otherstring"
}
var zipcode = zip;
var weatherUrl = "http://api.aerisapi.com/places/postalcodes/" + zipcode + "?client_id=" + credentials.client_id + "&client_secret=" + credentials.client_secret;
weather = Meteor.http.get(weatherUrl, function (error, result) {
if(error) {
console.log('http get FAILED!');
}
else {
console.log('http get SUCCES');
if (result.statusCode === 200) {
console.log('Status code = 200!');
console.log(result.content);
return result.content;
}
}
});
return weather;
}
});
For some reason, this does not return the results even though they exist and the http call works: console.log(result.content); does indeed log the results.
(Client folder)
Meteor.call('getWeather', somezipcode, function(error, results) {
if (error)
return alert(error.reason);
Session.set('weatherResults', results);
});
Of course here, the session variable ends up being empty.
(Note that this part of the code seems to be fine as it returned appropriately if I hard coded the return with some dummy string in the method.)
Help?
In your example Meteor.http.get is executed asynchronously.
See docs:
HTTP.call(method, url [, options] [, asyncCallback])
On the server, this function can be run either synchronously or
asynchronously. If the callback is omitted, it runs synchronously and
the results are returned once the request completes successfully. If
the request was not successful, an error is thrown
Switch to synchronous mode by removing asyncCallback:
try {
var result = HTTP.get( weatherUrl );
var weather = result.content;
} catch(e) {
console.log( "Cannot get weather data...", e );
}
Kuba Wyrobek is correct, but you can also still call HTTP.get asynchronously and use a future to stop the method returning until the get has responded:
var Future = Npm.require('fibers/future');
Meteor.methods({
getWeather: function(zip) {
console.log('getting weather');
var weather = new Future();
var credentials = {
client_id: "string",
client_secret: "otherstring"
}
var zipcode = zip;
var weatherUrl = "http://api.aerisapi.com/places/postalcodes/" + zipcode + "?client_id=" + credentials.client_id + "&client_secret=" + credentials.client_secret;
HTTP.get(weatherUrl, function (error, result) {
if(error) {
console.log('http get FAILED!');
weather.throw(error);
}
else {
console.log('http get SUCCES');
if (result.statusCode === 200) {
console.log('Status code = 200!');
console.log(result.content);
weather.return(result);
}
}
});
weather.wait();
}
});
There's not really much advantage to this method over a synchronous get in this case, but if you're ever doing something on the server which can benefit from something like an HTTP call running asynchronously (and thus not blocking the rest of the code in your method), but you still needs to wait for that call to return before the method can, then this is the right solution. One example would be where you need to execute multiple non-contingent gets, which would all have to wait for each other to return one by one if executed synchronously.
More here.
Sometimes asynchronous calls are preferable. You can use async/await syntax for that, and you need to promisify HTTP.get.
import { Meteor } from 'meteor/meteor';
import { HTTP } from 'meteor/http';
const httpGetAsync = (url, options) =>
new Promise((resolve, reject) => {
HTTP.get(url, options, (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
Meteor.methods({
async 'test'({ url, options }) {
try {
const response = await httpGetAsync(url, options);
return response;
} catch (ex) {
throw new Meteor.Error('some-error', 'An error has happened');
}
},
});
Notice that meteor test method is marked as async. This allows using await operator inside it with method calls which return Promise. Code lines following await operators won't be executed until returned promise is resolved. In case the promise is rejected catch block will be executed.

Categories

Resources