chain async operations using native JavaScript Promise - javascript

I've following two async operations and then final onResult and onFault defined. How can I chain following two async operations getConnection and then select and then finally calling onResult or onFault
Edit need help in promisifying following sequence.
new Promise(this.getConnection)
.then(this.select)
.then(this.onResult)
getConnection: function(resolve, reject) {
console.log('get connection')
if(database = common.model.connections.Sync.getConnection()) {
database.transaction(function(transaction){
resolve(transaction);
});
} else {
reject("Connection Error");
}
},
select: function(transaction) {
console.log('select', transaction)
var self = this;
var query = "SELECT * FROM configuration WHERE key = 'schedule'";
self.transaction.executeSql(query, [], function(transaction, resultSet){self.selectTransactionComplete(transaction, resultSet)}, function(){self.selectTransactionError()});
},
selectTransactionComplete: function(transaction, resultSet) {
console.log('select transaction complete')
if(resultSet.rows.length == 0) {
this.onResult(false);
} else if(new Date().getTime() - new Date(common.Config.getLastSync()).getTime() > resultSet.rows.item(0).value) {
this.onResult(true);
}
},
selectTransactionError: function(error) {console.log(this.constructor.NAME, this.selectSQL); console.log(error);},
onResult: function(data) {
console.log(data);
},
onFault: function(info) {
console.log(info)
}

after trying couple of things, is this how it's supposed to be done? please review
this.getConnection()
.then(this.select, this.getConnectionError)
.then(this.selectTransactionComplete, this.selectTransactionError)
getConnection: function() {
console.log('get connection')
var promise = new Promise(function(resolve, reject){
try {
database = common.model.connections.Sync.getConnection();
database.transaction(function(transaction){
resolve(transaction);
});
} catch(error) {
reject("Connection Error");
}
});
return promise;
},
getConnectionError: function(info) {
console.log("connectionError");
this.onFault();
},
select: function(transaction) {
console.log('select')
var self = this;
var promise = new Promise(function(resolve, reject){
var query = "SELECT * FROM configuration WHERE key = 'schedule'";
transaction.executeSql(query, [], function(transaction, resultSet){resolve(resultSet)}, function(error){reject(error)});
});
return promise;
},
selectTransactionComplete: function(resultSet) {
console.log('selectTransactionComplete')
/*if(resultSet.rows.length == 0) {
this.onResult(false);
} else if(new Date().getTime() - new Date(common.Config.getLastSync()).getTime() > resultSet.rows.item(0).value) {
this.onResult(true);
}*/
},
selectTransactionError: function(error) {
console.log('selectTransactionError')
//console.log(this.constructor.NAME, this.selectSQL);
//console.log(error);
},
onResult: function(data) {
console.log('onResult')
},
onFault: function(info) {
console.log('onFault')
}

If you are using a promise library like q, the way you would go about chaining the promises is as below;
getConnection: function() {
var deferred = Q.Defer();
console.log('get connection')
try {
database = common.model.connections.Sync.getConnection();
database.transaction(function(transaction){
deferred.resolve(transaction);
});
} catch(error) {
deferred.reject("Connection Error");
}
return deferred.promise;
}
when you call you would do something like below;
Q.when(getConnection)
.then(function(result){
// handle success or resolve
}, function(error){
// handle rejection.
};
Also I suggest reading the common js promises specification

Related

Promise resolved before entire chain of promises is resolved

I have a chain of promises as a function in my app. Each of my service functions returns a deferred.promise;
Considering the following scenario I have where main getUser service calls getUserPreferences and getUserFavourites asynchronously, the console.log after resolving getUserData is being resolved before getUserFavourites even responds! Shouldn't the promise in getUserData be resolved once getUserFavourites responds?
In fact 'got all user data' from the console.log is in the console before getUserFavourites is called. Literally straight after getUser responds almost like getUserData().then( only resolves the top level promise and make the underlying 2 asynchronous...
What am I doing wrong here?
var user = 'blabla';
function getUserData() {
var deferred = $q.defer();
getUser(user).then(
function(response) {
getUserPreferences(response.user).then(
function(preferences) {
console.log('preferences', preferences);
},
function() {
deferred.reject();
}
);
getUserFavourites(response.user).then(
function(favourites) {
deferred.resolve();
console.log('favourites', favourites);
},
function() {
deferred.reject();
}
);
},
function() {
deferred.reject();
}
);
return deferred.promise;
}
getUserData().then(
function() {
console.log('got all user data');
}
);
A way to fix that is to use the async/await to make the code look synchronous.
var user = 'blabla';
async function getUserData() {
try {
var deferred = $q.defer();
let userInfo = await getUser(user)
let userPrefs = await getUserPreferences(userInfo.user)
console.log('preferences', userPrefs);
let userFavourites = await getUserFavourites(userInfo.user)
deferred.resolve();
console.log('favourites', userFavourites);
return deferred.promise;
} catch (error) {
deferred.reject();
}
}
getUserData().then(
function() {
console.log('got all user data');
}
);
Use $q.all to return a composite promise:
function getUserData() {
return getUser(user).then(function(response) {
var preferencesPromise = getUserPreferences(response.user);
var favouritesPromise = getUserFavourites(response.user);
return $q.all([preferencesPromise, favouritesPromise]);
});
}
Then extract the data from the composite promise:
getUserData().then([preferences, favourites] => {
console.log('got all user data');
console.log(preferences, favourites);
}).catch(function(error) {
console.log(error);
});
The $q.all method returns a single promise that will be resolved with an array/hash of values, each value corresponding to the promise at the same index/key in the promises array/hash. If any of the promises is resolved with a rejection, this resulting promise will be rejected with the same rejection value.
For more information, see
AngularJS $q Service API Reference - $q.all
You must RETURN the nested promise in order to have a chain.
The problem here is you have 2 nested promises so you will need to return a Promise.all (or $q.all in your case) taking an array of the 2 promises returned by getUserPreferences and getUserFavorites:
var user = 'blabla';
function getUserPreferences(){
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve({color: 'green'});
},500);
});
}
function getUserFavorites(){
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve([{id: 1, title: 'first favorite'}, {id: 2, title: 'second favorite'}]);
},500);
});
}
function getUser(){
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve(user);
},500);
});
}
function getUserData() {
return getUser().then(
function(user) {
console.log(user);
var prefPromise = getUserPreferences(user).then(
function(preferences) {
console.log('preferences', preferences);
return preferences;
},
function(error) {
console.log("Error getting preferences");
throw error;
}
);
var favPromise = getUserFavorites(user).then(
function(favourites) {
console.log('favourites', favourites);
return favourites;
},
function(error) {
console.log("Error getting favorites");
throw error;
}
);
return Promise.all([
prefPromise,
favPromise
]);
},
function(err) {
console.log("Error getting user");
throw err;
}
);
}
getUserData().then(
function(results) {
console.log(results);
}
);
Note that for demo purpose I am using es6 Promise instead of angular $q but the spirit is the same:
$q.defer() => new Promise()
$q.all => Promise.all
As the Promise pattern is great to simplify async code and make it look like synchronous code, you can simplify the upper example with something like:
var user = { name: 'blabla'};
function getUserPreferences(user){
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve({color: 'green'});
},500);
});
}
function getUserFavorites(user){
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve([{id: 1, title: 'first favorite'}, {id: 2, title: 'second favorite'}]);
},500);
});
}
function getUser(){
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve(user);
},500);
});
}
function getUserData() {
return getUser()
.then(user => { // user is resolved
// running parallel promises to get user infos:
return Promise.all([
user,
getUserPreferences(user),
getUserFavorites(user)
]);
})
.then(results => {
// wrapping the results into something more semantic:
let userData = results[0];
userData.prefs = results[1];
userData.favs = results[2];
return userData;
});
}
getUserData().then(
function(userData) {
console.log('Final result:');
console.log(userData);
}
);

Javascript Execute at end of Async Loop

I'm awful with Async code in Javascript and have been stuck on something for a while now.
I'm working with WebSql and just going through database initialization steps but one of the loops is not executing in the way I expect it to.
$(document).ready(function() {
initdatabase();
});
function initdatabase() {
var db = window.openDatabase("nothing", "1.0", "nothing", 2 * 1024 * 1024);
db.transaction(function(trans) {
trans.executeSql("CREATE TABLE", [], function(trans, result) {
// success
defaultdata(db);
}, function(error) {
// failed
});
});
}
function defaultdata(db) {
db.transaction(function(trans) {
var lo_data = [
{code:"CODE01", desc:"Code 01 Desc"},
{code:"CODE02", desc:"Code 02 Desc"},
{code:"CODE03", desc:"Code 03 Desc"}
];
for(i = 0; i < lo_data.length; i++) {
trans.executeSql("INSERT", [lo_data[i].code, lo_data[i].desc], function(trans, result) {
// success
console.log("INS : " + i);
}, function(error) {
// failed
});
}
console.log("END");
});
}
But the log to indicate the end is executing before the for loop has finished. If I try validate that the data has been inserted I always get fails because the loop hasn't completed the inserts.
Google says that async code should be handled with promises but I can't find examples of promises being used in an instance like this.
Any help would be greatly appreciated
Convert each callback into a promise, and then use Promise.all
const loDataPromises = lo_data.map(({ code, desc }) => {
return new Promise((resolve, reject) => {
trans.executeSql(
"INSERT",
[code, desc],
function(trans, result) {
console.log('success');
resolve();
},
function(error) {
console.log('failed');
reject();
}
);
});
});
Promise.all(loDataPromises)
.then(() => {
console.log('all done');
});
I haven't been able to find any clear code examples on the internet so I wanted to post the working version here as the answer. Hopefully it can benefit someone also trying to understand promises and promise loops.
After a complete overhaul I've managed to get it working in a way that makes sense. I've change it to be executed as a promise chain and then the function with the for loop is utilizing the promise all logic.
$(document).ready(function() {
////
// promise chain
////
console.log("BEGIN");
f_initdatabase().then(function(result) {
return f_defaultdata(result.db);
}).then(function(result) {
console.log("END");
}).catch(function(result) {
// abandon all hope
});
});
////
// single promise usage
////
function f_initdatabase() {
return new Promise(function(resolve, reject) {
console.log(" INIT DB");
var lo_result = {db:null};
var lv_db = window.openDatabase("thenothing", "1.0", "The Nothing DB", 2 * 1024 * 1024);
lv_db.transaction(function(trans) {
trans.executeSql ("create table if not exists dummydata (dd_idno integer primary key, dd_code text not null, dd_desc text not null)", [], function(trans, results) {
console.log(" INIT DB : DONE");
lo_result.db = lv_db;
resolve(lo_result);
}, function(error) {
lo_result.db = null;
reject(lo_result);
});
});
});
}
////
// loop promise all usage
////
function f_defaultdata(lv_db) {
return new Promise(function(resolve, reject) {
console.log(" DEF DATA");
var lo_result = {db:null};
lv_db.transaction(function(trans) {
var la_promises = [];
var lo_data = [
{dd_code:"CODE01", dd_desc:"Code 01 Desc"},
{dd_code:"CODE02", dd_desc:"Code 02 Desc"},
{dd_code:"CODE03", dd_desc:"Code 03 Desc"}
];
for(i = 0; i < lo_data.length; i++) {
console.log(" INS : " + i);
trans.executeSql (" insert into dummydata (dd_code, dd_desc) values (?, ?)", [lo_data[i].dd_code, lo_data[i].dd_desc], function(trans, results) {
la_promises.push(resolve(lo_result));
}, function(error) {
la_promises.push(reject(lo_result));
});
}
Promise.all(la_promises).then(function(results) {
console.log(" DEF DATA : DONE");
lo_result.db = lv_db;
resolve(lo_result);
}).catch(function() {
lo_result.db = null;
reject(lo_result);
});
});
});
}
This gives the output according to the flow needed
BEGIN
INIT DB
INIT DB : DONE
DEF DATA
INS : 0
INS : 1
INS : 2
DEF DATA : DONE
END

Node.js using multiple promises

I currently try to use two kind of promises. One for the overall convensation, and one to get the userids to usernames.
The code works when its ran first time, and is looking something like this:
The output above is what i wanted.
But when i run the query again, i get this:
what is wrong with my code? what do i need to do to make the promises go correctly?
code:
function getconvensations(userid) {
return new Promise(function(resolve, reject) {
convensations.find({members: userid}).lean().then(function (convensations) {
var promises = convensations.map(function (currentConvensation) {
return new Promise(function (resolve, reject) {
var realnames = [];
var usernames = currentConvensation.members.map(function (CurrentUser) {
Core.userDetails(CurrentUser).then(function (user) {
realnames.push(user.username);
console.log("RESOLVING USERNAMES");
resolve();
});
});
Promise.all(usernames).then(function () {
console.log("GET LATEST MESSAGE");
latestMessage(currentConvensation._id).then(function (message) {
console.log("Messages: ");
console.log(message);
currentConvensation.spesificmessage = message;
currentConvensation.users = realnames;
currentConvensation.otherend = (currentConvensation.members[0] == userid ? realnames[0] : realnames[1]);
console.log("RESOLVE LATEST MESSAGE");
resolve();
});
});
});
});
Promise.all(promises).then(function () {
console.log("FINISHED CONVENSATIONS");
console.log("-------------------------");
console.log(convensations);
console.log("-------------------------");
return resolve(convensations);
}).catch(console.error);
});
});
}
caller:
} else if(action == 'getconvensations') {
Messages_model.getconvensations(req.user._id).then(function (response) {
res.json(response);
});
}
You have a race condition here:
new Promise(function (resolve, reject) {
…
currentConvensation.members.map(function (CurrentUser) {
Core.userDetails(CurrentUser).then(function (user) {
resolve();
});
});
…
latestMessage(currentConvensation._id).then(function (message) {
resolve();
});
…
});
There are arbitrarily many tasks executing concurrently, and the first promise that fulfills will call resolve().
The solution is to avoid the Promise constructor antipattern and never ever call new Promise or resolve manually! Instead, chain promise callbacks to each other using the then method, and return new promises from every function to allow the caller to wait for them.
function getconvensations(userid) {
return convensations.find({members: userid}).lean().then(function (convensations) {
// ^^^^^^
var promises = convensations.map(function (currentConvensation) {
var usernamepromises = currentConvensation.members.map(function (CurrentUser) {
console.log("GETTING USERNAME");
return Core.userDetails(CurrentUser).then(function (user) {
// ^^^^^^
console.log("FULFILLED USERNAME");
return user.username;
// ^^^^^^
});
});
return Promise.all(usernamepromises).then(function (realnames) {
// ^^^^^^
console.log("FULFILLED ALL USERNAMES");
currentConvensation.users = realnames;
currentConvensation.otherend = (currentConvensation.members[0] == userid ? realnames[0] : realnames[1]);
console.log("GETTING LATEST MESSAGE");
return latestMessage(currentConvensation._id);
// ^^^^^^
}).then(function (message) {
console.log("FULFILLED LATEST MESSAGE");
console.log("Message: ", message);
currentConvensation.spesificmessage = message;
});
});
return Promise.all(promises).then(function () {
// ^^^^^^
console.log("FINISHED ALL CONVENSATIONS");
console.log("-------------------------");
console.log(convensations);
console.log("-------------------------");
return convensations;
// ^^^^^^
});
});
}

Promise inside promise

I am trying to write this code with Promise. but I don't know how to write promise inside Promise and loop.
I tried to think like this but insertBook function become asynchronously.
How can I get bookId synchronously?
update: function(items, quotationId) {
return new Promise(function(resolve, reject) {
knex.transaction(function (t) {
Promise.bind(result).then(function() {
return process1
}).then(function() {
return process2
}).then(function() {
var promises = items.map(function (item) {
var people = _.pick(item, 'familyName', 'firstNumber', 'tel');
if (item.type === 'book') {
var book = _.pick(item, 'name', 'bookNumber', 'author');
var bookId = insertBook(t, book);
var values = _.merge({}, people, {quotation: quotationId}, {book: bookId});
} else {
var values = _.merge({}, people, {quotation: quotationId});
}
return AModel.validateFor(values);
});
return Promise.all(promises);
}).then(function(items) {
var insertValues = items.map(function (item) {
return People.columnize(item);
});
return knex('people').transacting(t).insert(insertValues);
}).then(function() {
return process5
}).then(function() {
...........
}).then(function() {
t.commit(this);
}).catch(t.rollback);
}).then(function (res) {
resolve(res);
}).catch(function(err) {
reject(err);
});
});
}
function insertBook(t, book){
return Promise.bind(this).then(function () {
return Book.columnizeFor(book);
}).then(function (value) {
return knex('book').transacting(t).insert(value, "id");
});
}
You dont need to get bookid synchronously, you can handle it asynchronously correctly. Also, it is possible you want all book insertions happen sequentially, so I refactored the Promise.all part. (done that just to give you an idea. Promise.all should work fine if insertions in parallel are allowed). Furthermore, I think you shouldn't use Promise.bind. To be honest I dont even know what it does, one thing for sure: it doesn't work with standard promises. So here is an example how I think it should work:
update: function(items) {
return new Promise(function(resolve) {
knex.transaction(function (t) {
resolve(Promise.resolve().then(function() {
return process1;
}).then(function() {
return process2;
}).then(function() {
var q = Promise.resolve(), results = [];
items.forEach(function (item) {
q = q.then(function() {
var book = _.pick(item, 'name', 'bookNumber', 'author');
return insertBook(t, book);
}).then(function(bookId) {
var people = _.pick(item, 'familyName', 'firstNumber', 'tel');
var values = _.merge({}, people, {book: bookId});
return AModel.validateFor(values);
}).then(function(item) {
results.push(item);
});
});
return q.then(function() {
return results;
});
}).then(function(items) {
return process4
}).then(function() {
t.commit(result);
}).catch(function(e) {
t.rollback(e);
throw e;
}));
});
});
}
function insertBook(t, book){
return Promise.resolve().then(function () {
return Book.columnizeFor(book);
}).then(function (value) {
return knex('book').transacting(t).insert(value, "id");
});
}
Assuming that insertBook returns a promise you could do
var people = _.pick(item, 'familyName', 'firstNumber', 'tel');
if (item.type === 'book') {
var book = _.pick(item, 'name', 'bookNumber', 'author');
return insertBook(t, book)
.then(bookId => _.merge({}, people, {quotation: quotationId}, {book: bookId}))
.then(AModel.validateFor)
} else {
return Promise.resolve(_.merge({}, people, {quotation: quotationId}))
.then(AModel.validateFor)
}

Nested promise in a for loop not behaving as expected

I am having trouble extending a Promise inside a .then(). I am trying to perform DB updates in a for-loop and then close the database after all records are processed. However the application exits with process.exit() right away which means that process.exit() was executed even before all db updates were finished. I am pretty sure I am doing something wrong with the nested promise.
var myDB;
function doSomething() {
return MongoClient.connect(DB_CONNECTION).then(function(db) {
myDB = db;
var collection = db.collection(COLLETION_NAME);
for (var i = 0; i < 10; i++) {
promise.then(function{
collection.update({
symbol: items[i].symbol
}, {
$set: {
value: 123
}
}, {
upsert: true
});
});
}
})
}
var promise = doSomething();
promise.then(function(){
console.log("DONE");
myDB.close();
process.exit();
});
It looks like you are getting a promise back from the MongoClient.connect method so why not use that to chain together. I've put a quick sample together below based on your code:
function doSomething(db) {
return new Promise(function(resolve, reject){
var collection = db.collection(COLLETION_NAME);
for (var i = 0; i < 10; i++) {
collection.update({
symbol: items[i].symbol
}, {
$set: {
value: 123
}
}, {
upsert: true
});
}
resolve(db);
})
}
function connectToDB() {
return MongoClient.connect(DB_CONNECTION);
}
function closeDB(db) {
return new Promise(function(resolve, reject){
db.close();
resolve();
});
}
connectToDB().then(function(db){
return doSomething(db);
}).then(function(db){
return closeDB(db);
}).then(function(){
console.log("DONE");
process.exit();
}).catch(function(error){
console.log('Something went wrong: ' + error);
});
Updated code as per #RayonDabre 's suggestion
function doSomething() {
return MongoClient.connect(DB_CONNECTION).then(function(db) {
myDB = db;
var collection = db.collection(COLLECTION_NAME);
var promises = [];
for (var i = 0; i < 10; i++) {
var innerPromise = collection.update({
symbol: items[i].symbol
}, {
$set: {
value: 123
}
}, {
upsert: true
});
promises.push(innerPromise);
}
return Promise.all(promises);
});
}
var promise = doSomething();
promise.then(function(){
console.log("DONE");
myDB.close();
process.exit();
});

Categories

Resources