Retrieve data from a callback thats in a promise? - javascript

I have the following piece of code right now:
const Promise = require('bluebird');
const readFile = Promise.promisify(fs.readFile);
recordPerfMetrics: function(url) {
var self = this;
var perf, loadTime, domInteractive, firstPaint;
var perfData = {};
readFile('urls.txt', 'UTF-8').then(function (urls, err) {
if (err) {
return console.log(err);
}
var urls = urls.split("\n");
urls.shift();
urls.forEach(function(url) {
console.log(url);
self.getStats(url).then(function(data) {
data = data[0];
loadTime = (data.loadEventEnd - data.navigationStart)/1000 + ' sec';
firstPaint = data.firstPaint;
domInteractive = (data.domInteractive - data.navigationStart)/1000 + ' sec';
perfData = {
'URL' : url,
'firstPaint' : firstPaint,
'loadTime' : loadTime,
'domInteractive' : domInteractive
};
console.log(perfData);
}).catch(function(error) {
console.log(error);
});
});
// console.log(colors.magenta("Starting to record performance metrics for " + url));
// this.storePerfMetrics();
});
},
getStats: function(url) {
return new Promise(function(resolve, reject){
console.log("Getting data for url: ",url);
browserPerf(url, function(error, data) {
console.log("inside browserPerf", url);
if (!error) {
resolve(data);
} else {
reject(error);
}
}, {
selenium: 'http://localhost:4444/wd/hub',
browsers: ['chrome']
});
});
},
This is basically reading urls from a file and then calling a function browserPerf whose data being returned is in a callback function.
The console.log("Getting data for url: ",url); is in the same order as the urls that are stored in the file,
but the console.log("inside browserPerf", url); is not conjunction as the same and as expected.
I expect the order of the urls to be:
console.log(url);
console.log("Getting data for url: ",url);
console.log("inside browserPerf", url);
But for reason only the first two are executed in order but the third one is fired randomly after all are being read.
Any idea what i am doing wrong here?

Since you are using Bluebird, you can replace your .forEach() loop with Promise.mapSeries() and it will sequentially walk through your array waiting for each async operation to finish before doing the next one. The result will be a promise who's resolved value is an array of results. You also should stop declaring local variables in a higher scope when you have async operations involved. Declare them in the nearest scope practical which, in this case is the scope in which they are used.
const Promise = require('bluebird');
const readFile = Promise.promisify(fs.readFile);
recordPerfMetrics: function() {
var self = this;
return readFile('urls.txt', 'UTF-8').then(function (urls) {
var urls = urls.split("\n");
urls.shift();
return Promise.mapSeries(urls, function(url) {
console.log(url);
return self.getStats(url).then(function(data) {
data = data[0];
let loadTime = (data.loadEventEnd - data.navigationStart)/1000 + ' sec';
let firstPaint = data.firstPaint;
let domInteractive = (data.domInteractive - data.navigationStart)/1000 + ' sec';
let perfData = {
'URL' : url,
'firstPaint' : firstPaint,
'loadTime' : loadTime,
'domInteractive' : domInteractive
};
console.log(perfData);
return perfData;
}).catch(function(error) {
console.log(error);
throw error; // keep the promise rejected
});
});
// console.log(colors.magenta("Starting to record performance metrics for " + url));
// this.storePerfMetrics();
});
},
getStats: function(url) {
return new Promise(function(resolve, reject){
console.log("Getting data for url: ",url);
browserPerf(url, function(error, data) {
console.log("inside browserPerf", url);
if (!error) {
resolve(data);
} else {
reject(error);
}
}, {
selenium: 'http://localhost:4444/wd/hub',
browsers: ['chrome']
});
});
},
You would use this like this:
obj.recordPerfMetrics().then(function(results) {
// process results array here (array of perfData objects)
}).catch(function(err) {
// error here
});
Summary of changes:
Return promise from recordPefMetrics so caller can get data
Use Promise.mapSeries() instead of .forEach() for sequential async operations.
Return promise from Promise.mapSeries() so it is chained with prior promise.
Move variable declarations into local scope so there is no change of different async operations stomping on shared variables.
Rethrow .catch() error after logging so the reject propagates
return perfData so it becomes the resolved value and is available in results array.

Related

Call JavaScript function when all Ajax calls finished

need so help with some JavaScript code that isn't working as expected.
The Application sends files names to the server via Ajax calls to be processed but I want to tell the database that current import has been completed when they are all done. The issue I'm having is the code to update the database with the state runs before any of the ajax calls run.
I have ready in a number of area on the web that you could potentially create a promise array to track that but JavaScript honestly being a weak point of mine, I'm not sure how to I would go about implementing it.
below are my current code snippets:
function that loops through file names:
function importFiles() {
serverImporterInit();
let files = getFiles();
if (files) {
showImportProgressModal(files);
let looper = $.Deferred().resolve();
$.when.apply($, $.map(files, function (file, index) {
let fileStatusElement = document.getElementById('file-' + index + '-status');
let fileErrorElement = document.getElementById('file-' + index + '-error');
looper = looper.then(function () {
setTimeout(function () {
return import_file_request(file, fileStatusElement, fileErrorElement);
}, 2000);
});
return looper;
})).then(function () {
});
}
}
ajax call to the server:
function import_file_request(file, element, errorElement) {
let deferred = $.Deferred();
fileImportStatusInProgress(element);
$.ajax({
type: 'POST',
url: '/importer/manual_import',
data: {'file': file.toString()},
success: function(data) {
fileImportStatusSuccess(element);
deferred.resolve(data);
},
error: function (error) {
fileImportStatusFailed(error, element, errorElement);
deferred.reject(error);
}
});
return deferred.promise();
}
Both of these functions have been derived from other tutorials across the web but I'm not entirely sure if they ever did what I originally intended as I have only just got around to trying to track the completion status due to another requirement.
Any help would be great. Also if there any other details I can include to make this question a little better for other please let me know and I will update accordingly.
Update
I have tried to update the code to use a promise array but still having no luck.
File loop:
const importFiles = async (files) => {
serverImporterInit()
const filesLength = files.length
showImportProgressModal(files);
for (let i = 0; i < filesLength; i++) {
const requests = files.map((file) => {
let fileStatusElement = document.getElementById('file-' + i + '-status');
let fileErrorElement = document.getElementById('file-' + i + '-error');
return import_file_request(file, fileStatusElement, fileErrorElement) // Async function to import file
.then(console.log(file + " successfully imported"))
.catch(e => console.log('Error'))
})
await Promise.all(requests)
.then(serverImporterClose())
.catch(e => console.log(''))
}
}
File import request to sever:
function import_file_request(file, element, errorElement) {
fileImportStatusInProgress(element);
return new Promise((resolve, reject) => {
$.ajax({
type: 'POST',
url: '/importer/manual_import',
data: {'file': file.toString()},
success: function(data) {
fileImportStatusSuccess(element);
resolve();
},
error: function (error) {
fileImportStatusFailed(error, element, errorElement);
reject();
}
});
})
}
You seem to be using jQuery so I try to give a jQuery based solution without the native promise. I think you should either use the native promise combined with the builtin fetch() function and no jQuery or jQuery alone.
The key is to use $.map() to give back an array of promises and then wait for all of them using $.when(). Also it is always important to return the jQuery promise.
function importFiles() {
var files = getFiles();
if (files) {
showImportProgressModal(files);
serverImporterInit();
var promises = $.map(files, function(file, index) {
let fileStatusElement = document.getElementById('file-' + index + '-status');
let fileErrorElement = document.getElementById('file-' + index + '-error');
return import_file_request(file, fileStatusElement, fileErrorElement)
})
$.when.apply($, promises).then(function(results) {
serverImporterClose();
results.forEach(function(result) {
if (result) {
console.log("yay, success");
}
else {
console.log("failed");
}
})
hideImportProgressModal(files);
});
}
}
function import_file_request(file, element, errorElement) {
fileImportStatusInProgress(element);
// MUST return the promise here
return $.ajax({
type: 'POST',
url: '/importer/manual_import',
data: {'file': file.toString()},
success: function(data) {
fileImportStatusSuccess(element);
return true;
},
error: function (error) {
fileImportStatusFailed(error, element, errorElement);
return false;
}
});
}

Parse Promise 'when' returns undefined (Javascript)

I'm trying to follow the 'when' example on Parse JavaScript SDK: Parse.Promise
with the following code:
GetFromDB2 = function(id) {
var DB2 = Parse.Object.extend("DB2");
var q = new Parse.Query(DB2);
q.get(id, {
success: function(res) {
return Parse.Promise.as(10);
},
error: function(res, err) {
console.log( err);
}
});
}
GetData = function() {
var DB1 = Parse.Object.extend("DB1");
var query = new Parse.Query(DB1);
query.equalTo("param", "close");
query.find().then(function(results) {
var promises = [];
_.each(results, function(res) {
promises.push(GetFromDB2(res.get("user")));
});
Parse.Promise.when(promises).then(function() {
console.log(arguments); // expect: [10, 10, ...]
})
});
};
The length of the array arguments is correct but not sure why its values are undefined.
As written, GetFromDB2() returns undefined. To deliver a value, it must return either a value or a promise.
At its simplest, to deliver 10, you could write :
function GetFromDB2(id) {
return 10;
}
But to be asynchronous, and still deliver 10, you need to return a promise that will resolve to 10 :
function GetFromDB2(id) {
return Parse.Promise.as(10);
}
Or the .get(id) query you really want :
function GetFromDB2(id) {
var DB2 = Parse.Object.extend('DB2');
var q = new Parse.Query(DB2);
return q.get(id).then(function(res) {
return 10;
});
//omit `.then(...)` entirely to deliver `res`
}
Now GetData() can be written as follows :
function GetData() {
var DB1 = Parse.Object.extend('DB1');
var query = new Parse.Query(DB1);
query.equalTo('param', 'close');
return query.find().then(function(results) {
var promises = _.map(results, function(res) {
return GetFromDB2(res.get('user'));
});
Parse.Promise.when(promises).then(function() {
console.log(arguments); // expect: [10, 10, ...]
}, function(err) {
console.log(err);
return err;
});
});
};
Notes:
promises = _.map(...) is more elegant than _.each(...) plus `promises.push(...).
moving the error handler into GetData() allows a greater range of possible errors to be handled, eg an error arising from query.find().
By returning a promise, GetData()'s caller is also informed of the eventual asynchronous outcome.

$q.all with nested promise also created with $q.all

The following function tries to return a promise that will only resolve when all the async HTTP calls have finished:
$scope.saveThat = function () {
var promises = [];
for (var i = 0; i < array.length; i++) {
var newPromise = $q.defer();
promises.push(newPromise);
// some more code...
(function (i, newPromise) {
$http(httpData)
.success(function (response) {
newPromise.resolve('Success');
})
.error(function (response) {
newPromise.reject('Error');
});
})(i, newPromise);
}
return $q.all(promises);
};
And the following snippet calls this function.
// save this...
var promise1 = $rootScope.saveThis(result.Person);
promise1.then(function (success) {
}, function (error) {
saveErrorMessage += 'Error saving this: ' + error + '.';
});
// save that...
var promise2 = $rootScope.saveThat(result.Person);
promise3.then(function (success) {
}, function (error) {
saveErrorMessage += 'Error saving that: ' + error + '.';
});
// wait until all promises resolve
$q.all([promise1, promise2])
.then(
function (success) {
$scope.$emit(alertEvent.alert, { messages: 'Saved successfully!', alertType: alertEvent.type.success, close: true });
}, function (error) {
$scope.$emit(alertEvent.alert, { messages: saveErrorMessage, alertType: alertEvent.type.danger });
});
The problem I have is that the second promise ($q.all([promise1, promise2])) resolves even when the promises in promise2 haven't resolved yet.
Because you are not creating an array of promise, Actually it contains a $q.defer() object. You should be using
promises.push(newPromise.promise);
instead of
promises.push(newPromise);
Also you need to avoid those anti-pattern, because you are creating $q object unnecessarily as you have promise object there which returned from the $http.get.
Code
$scope.saveThat = function() {
var promises = [];
for (var i = 0; i < array.length; i++) {
// some more code...
var promise = $http(httpData)
.then(function(response) {
return 'Success'; //returning data from success resolves that promise with data
}, function(response) {
return 'Error'; //returning data from error reject that promise with data
});
promises.push(promise); //creating promise array
}
return $q.all(promises); //apply $q.all on promise array
};

Look for Promise bluebird code review for node.js

When and where need to use new Promise(Function<Function resolve, Function reject> resolver) -> Promise
My Sample code:
userInfo.js
var Promise = require('bluebird');
var winston = require('winston');
var _ = require('lodash');
var request = Promise.promisify(require("request"));
exports.getWeather = function (data) {
var cityName = data.userProfile.city;
return request("http://0.0.0.0:3003/api/Weather/byCity?city=" + cityName).spread(function (res, body) {
var result = JSON.parse(body).data;
return _.merge(data, result);
});
};
exports.getUserProfile = function (userId) {
return new Promise(function (resolve, reject) {
request("http://0.0.0.0:3003/api/UserProfile/getUserProfile?id=" + userId).spread(function (res, body) {
var result = JSON.parse(body).data;
resolve(result);
});
})
};
exports.getEvents = function (data) {
var cityName = data.userProfile.city;
return request("http://0.0.0.0:3003/api/Events/byCity?city=" + cityName).spread(function (res, body) {
var result = JSON.parse(body).data;
return _.merge(data, result);
});
};
exports.getFashion = function (data) {
var gender = data.userProfile.gender;
return request("http://0.0.0.0:3003/api/Fashion/byGender?gender=" + gender).spread(function (res, body) {
var result = JSON.parse(body).data;
return _.merge(data, result);
});
};
exports.displayDetail = function (data) {
console.log(data);
};
Above code I try call in 2 way in promise
getUserProfile.js
var userInfo = require('./userInfo');
module.exports = function(){
return userInfo.getUserProfile(3)
.then(userInfo.getFashion)
.then(userInfo.getEvents)
.then(userInfo.getWeather)
.then(userInfo.displayDetail)
.catch(function (e) {
console.log('Error:');
console.error(e.stack)
})
.finally(function () {
console.log('done');
});
}
2nd way:
getUserInformation.js
var userInfo = require('./userInfo');
module.exports = function () {
return new Promise(function (resolve, reject) {
resolve(3);
})
.then(userInfo.getUserProfile)
.then(userInfo.getFashion)
.then(userInfo.getEvents)
.then(userInfo.getWeather)
.then(userInfo.displayDetail)
.catch(function (e) {
console.log('Error:');
console.error(e.stack)
})
.finally(function () {
console.log('done');
});
};
getDetails.js
var userInfo = require('./getUserInformation');
userInfo()
.then(function(){
console.log('getDetails done')
})
.catch(function (e) {
console.log('Error:');
console.error(e.stack)
})
.finally(function () {
console.log('done');
});
please let me know what the difference and is there any issues by using these way?
exports.getUserProfile = function (userId) {
return new Promise(function (resolve, reject) {
request("http://0.0.0.0:3003/api/UserProfile/getUserProfile?id=" + userId).spread(function (res, body) {
var result = JSON.parse(body).data;
resolve(result);
});
})
};
Please don't do this. Just return from the callback, and return the promise created by then, like you have done it in your other three methods.
return userInfo.getUserProfile(3)
.then(…)
vs.
return new Promise(function (resolve, reject) {
resolve(3);
})
.then(userInfo.getUserProfile)
.then(…)
Well, the first one is much more readable and concise. They're pretty much equivalent except for the case that getUserProfile does throw synchronously, which it shouldn't anyway. Also in the first case getUserProfile is invoked as a method on userInfo, while in the second case it's just a callback function, the this in the calls will be different.
The second pattern can be tremendously simplified though by using Promise.resolve instead of the new Promise constructor:
return Promise.resolve(3)
.then(userInfo.getUserProfile)
.then(…)
This is totally fine, and aligns better with the rest of the chain. Speaking of which, …
.then(userInfo.getFashion)
.then(userInfo.getEvents)
.then(userInfo.getWeather)
where each of the functions returns a promise that resolves with
additional data merged into its argument
is not exactly the best way to solve this. Yes, it ensures that these three functions are called after each other, and is an acceptable pattern for that case. However, in your case you're mixing the request calls to the API with that argument-extraction and result-merging in the same function; which by the separation of concerns you shouldn't. Rather make the functions pure
exports.… = function (arg) {
return request("http://0.0.0.0:3003/api/…?…=" + arg).spread(function (res, body) {
return JSON.parse(body).data;
});
};
And now you can combine them separately - and not only in sequence, but also in parallel:
userInfo.getUserProfile(3)
.then(function(data) {
var p = data.userProfile;
return Promise.prop({
userProfile: 0,
fashion: userInfo.getFashion(p.gender), // `\
events: userInfo.getEvents(p.city), // }=> execute requests in parallel
weather: userInfo.getWeather(p.city) // ./
});
})
.then(userInfo.displayDetail)
.catch(function (e) {
console.error('Error:', e.stack)
});
The first way is much more readable, and there's no benefit to starting the chain with a promise that returns a constant as in your second way.
They both do effectively the same thing, with one caveat: In your second example (Starting the chain with a Promise), the getUserProfile call will be run on the next tick (Similar to if you'd thrown it in a setTimeout 0) rather than atomically.

Q.js: How can I rewrite an async series flow in Q.js?

In an attempt to grasp Q.js, I'd like to convert the following code using async.series in Q.js. Basically I create a folder if it doesn't exist (using mkdirp), move a file into a backup folder and save a file into a main folder.
var async = require('async');
var fs = require('fs');
var path = require('path');
var sessiondId = new Date().getTime() % 2 == 0 ? new Date().getTime().toString() : '_1234';
var backupFolder = path.join(__dirname,sessiondId);
var backupFullPath = path.join(backupFolder,'a.txt');
var fullPath = path.join(__dirname,'main','a.txt');
var mkdirp = require('mkdirp');
async.series({
createOrSkip: function(callback) {
mkdirp(backupFolder, function (err, dir) {
if(err) {
callback(err, null);
} else {
callback(null, {created: !!dir, folderAt: backupFolder});
}
});
},
move: function(callback) {
fs.rename(fullPath, backupFullPath, function(err) {
if(err) {
callback(err, null);
} else {
callback(null, {backupAt: backupFullPath});
}
});
},
write: function(callback) {
fs.writeFile(fullPath, 'abc', function(err) {
if (err) {
callback(err, null);
} else {
callback(null, {saveAt: fullPath});
}
});
}
}, function(err, result) {
console.log(result);
});
Actually I don't know where to start. Thanks for your help.
R.
The key is to convert the node.js functions to return promises using Q.denodeify before you start, this means the header of your file should look like:
var Q = require('q')
var fs = require('fs');
var path = require('path');
var sessiondId = new Date().getTime() % 2 == 0 ? new Date().getTime().toString() : '_1234';
var backupFolder = path.join(__dirname,sessiondId);
var backupFullPath = path.join(backupFolder,'a.txt');
var fullPath = path.join(__dirname,'main','a.txt');
var mkdirp = Q.denodeify(require('mkdirp'));
var rename = Q.denodeify(fs.rename);
var writeFile = Q.denodeify(fs.writeFile);
That change wouldn't be needed if node.js natively supported promises.
Option 1
// createOrSkip
mkdirp(backupFolder)
.then(function (dir) {
// move
return rename(fullPath, backupFullPath);
})
.then(function () {
// write
return writeFile(fullPath, 'abc');
})
.done(function () {
console.log('operation complete')
});
I don't think it gets much simpler than that. Like #Bergi said though, it's more similar to "waterfall". If you want the exact behavior of series (but with promises) you'll have to use something like Option 2 or Option 3.
Option 2
You could write out the code manually to save the results. I usually find that, although this requires a little extra writing, it's by far the easiest to read:
var result = {}
mkdirp(backupFolder)
.then(function (dir) {
result.createOrSkip = {created: !!dir, folderAt: backupFolder};
return rename(fullPath, backupFullPath);
})
.then(function () {
result.move = {backupAt: backupFullPath};
return writeFile(fullPath, 'abc');
})
.then(function () {
result.write = {saveAt: fullPath};
return result;
})
.done(function (result) {
console.log(result);
});
Option 3
If you find yourself using this sort of code all the time, you could write a very simple series helper (I've never found the need to do this personally):
function promiseSeries(series) {
var ready = Q(null);
var result = {};
Object.keys(series)
.forEach(function (key) {
ready = ready.then(function () {
return series[key]();
}).then(function (res) {
result[key] = res;
});
});
return ready.then(function () {
return result;
});
}
promiseSeries({
createOrSkip: function () {
return mkdirp(backupFolder).then(function (dir) {
return {created: !!dir, folderAt: backupFolder};
});
},
move: function () {
return rename(fullPath, backupFullPath)
.thenResolve({backupAt: backupFullPath});
},
write: function () {
return writeFile(fullPath, 'abc')
.thenResolve({saveAt: fullPath});
}
}).done(function (result) {
console.log(result);
});
I'd say once you've written the helper, the code is a lot clearer for promises than with all the error handling cruft required to work with callbacks. I'd say it's clearer still when you either write it by hand or don't keep track of all those intermediate results.
Summing Up
You may or may not think these examples are clearer than the async.series version. Consider how well you might know that function though. It's actually doing something pretty complex in a very opaque manner. I initially assumed that only the last result would be returned (ala waterfall) and had to look it up in the documentation of Async. I almost never have to look something up int the documentation of a Promise library.
Make each of your functions return a promise. Construct them with a Deferred:
function createOrSkip(folder) {
var deferred = Q.defer();
mkdirp(folder, function (err, dir) {
if(err) {
deferred.reject(err);
} else {
deferred.resolve({created: !!dir, folderAt: backupFolder});
}
});
return deferred.promise;
}
However, there are helper functions for node-style callbacks so that you don't need to check for the err yourself everytime. With Q.nfcall it becomes
function createOrSkip(folder) {
return Q.nfcall(mkdirp, folder).then(function transform(dir) {
return {created: !!dir, folderAt: backupFolder};
});
}
The transform function will map the result (dir) to the object you expect.
If you have done this for all your functions, you can chain them with then:
createOrSkip(backupfolder).then(function(createResult) {
return move(fullPath, backupFullPath);
}).then(function(moveResult) {
return write(fullPath, 'abc');
}).then(function(writeResult) {
console.log("I'm done");
}, function(err) {
console.error("Something has failed:", err);
});
Notice that this works like async's waterfall, not series, i.e. the intermediate results will be lost. To achieve that, you would need to nest them:
createOrSkip(backupfolder).then(function(createResult) {
return move(fullPath, backupFullPath).then(function(moveResult) {
return write(fullPath, 'abc');.then(function(writeResult) {
return {
createOrSkip: createResult,
move: moveResult,
write: writeResult
};
});
});
}).then(function(res){
console.log(res);
}, function(err) {
console.error("Something has failed:", err);
});

Categories

Resources