According to this answer, the promise has been created, but the method 'then'( also 'done') will be executed without waiting for the output from subprocess, I need to have a method which is to be called after completely executing all subprocess, how this can be accomplished using bluebird api?
Sample Code
var Promise = require('bluebird')
var exec = require('child_process').exec
// Array with input/output pairs
var data = [
['input1', 'output1'],
['input2', 'output2'],
.....
]
var PROGRAM = 'cat'
Promise.some(data.map(function(v) {
var input = v[0]
var output = v[1]
new Promise(function(yell, cry) {
exec('echo "' + input + '" | ' + PROGRAM, function(err, stdout) {
if(err) return cry(err)
yell(stdout)
})
}).then(function(out) {
if(out !== output) throw new Error('Output did not match!')
})
}),data.length)
.then(function() {
// Send succes to user if all input-output pair matched
}).catch(function() {
// Send failure to the user if any one pair failed to match
})
Here the 'then' function is executed immediately even before the subprocess is completed.
Promise.some() expects an array of promises as its first argument. You are passing the results of data.map() to it, but your callback to data.map() never returns anything so therefore .map() doesn't construct an array of promises and therefore Promise.some() has nothing to wait on so it calls it's .then() handler immediately.
Also, if you're going to wait for all the promises, then you might as well use Promise.all() instead.
This is what I think you want.
Changes:
Switch to Promise.all() since you want to wait for all the promises.
Return the new Promise so .map() will create the array of promises.
Moved the output check into the original promise so it can reject rather than throw and it just seems like that moves all the result checking into one place.
Added all missing semi-colons.
Changed cry and yell to resolve and reject so code would be more familiar to outsiders expecting normal promise names.
Here's the code:
var Promise = require('bluebird');
var exec = require('child_process').exec;
// Array with input/output pairs
var data = [
['input1', 'output1'],
['input2', 'output2']
];
var PROGRAM = 'cat';
Promise.all(data.map(function(v) {
var input = v[0];
var output = v[1];
return new Promise(function(resolve, reject) {
exec('echo "' + input + '" | ' + PROGRAM, function(err, stdout) {
if(err) {
reject(err);
} else if (stdout !== output) {
reject(new Error('Output did not match!'));
} else {
resolve(stdout);
}
});
});
})).then(function() {
// Send succes to user if all input-output pair matched
}).catch(function() {
// Send failure to the user if any one pair failed to match
});
Related
I have a sequence of function calls, connected with ES6 promises. Apparently, there is something wrong with this implementation, as API calls to the endpoint are not returning anything and the browser is stuck waiting for a response.
Please advise.
module.exports.insertTreatmentDetails = function (req, res) {
var doctorId = 10000
var departmentId = 10000
var procedureid = 10000
var hospitalSchema = new hospitalModel();
var p = new Promise(function (resolve, reject) {
counterSchema.getNext('Treatment.doctor.doctorId', collection, function (doctorId) {
doctorId = doctorId;
})
counterSchema.getNext('Treatment.departmentId', collection, function (departmentId) {
departmentId = departmentId
})
counterSchema.getNext('Treatment.procedureid', collection, function (procedureid) {
procedureid = procedureid
})
}).then(function () {
setData()
}).then(function (){
hospitalSchema.save(function (error, data) {
if (error) {
logger.error("Error while inserting record : - " + error)
return res.json({ "Message": error.message.split(":")[2].trim() });
}
else {
return res.json({ "Message": "Data got inserted successfully" });
}
});
});
};
The short answer is that you aren't calling resolve or reject inside the first promise in your chain. The promise remains in a pending state. Mozilla has a good basic explanation of promises.
How to Fix
It appears that you want to retrieve doctorId, departmentId, and procedureId before calling setData. You could try to wrap all three calls in one promise, checking whether all three have returned something in each callback, but the ideal is to have one promise per asynchronous task.
If it's feasible to alter counterSchema.getNext, you could have that function return a promise instead of accepting a callback. If not, I would recommend wrapping each call in its own promise. To keep most true to what your code currently looks like, that could look like this:
const doctorPromise = new Promise((resolve, reject) =>
counterSchema.getNext('Treatment.doctor.doctorId', collection, id => {
doctorId = id;
resolve();
}));
Then you could replace the first promise with a call to Promise.all:
var p = Promise.all([doctorPromise, departmentPromise, procedurePromise])
.then(setData)
.then(/* ... */);
Promises allow you to pass a value through to the next step, so if you wanted to get rid of your broadly-scoped variables (or set them in the same step where you call setData), you could just pass resolve as your callback to counterSchema.getNext and collect the values in the next step (also how you'd want to do it if you have counterSchema.getNext return a promise:
Promise.all([/* ... */])
.then(([doctorID, departmentID, procedureID]) => {
// If you aren't changing `setData`
doctorId = doctorID;
departmentId = departmentID;
procedureid = procedureID;
setData();
// If you are changing `setData`
setData(doctorID, departmentID, procedureID);
}).then(/* ... */).catch(/* I would recommend adding error handling */);
I have an array of promises, I use $q.all to execute all the promises then I do an action, somehow this action executed before the promises give answer back.
note: flightAPIService.getNearestAirports() return $http angular :
this.getNearestAirports = function (dataObj) {
console.log('Client::flight service:: get the nearest '+ dataObj.maxAirports+' airport for lat: '+dataObj.lat+'lng:'+dataObj.lng);
return $http.post('/getNearestAirports', dataObj);
//// the result return in JSON format (Server converted the response from XML to JSON)
//answer looks like:
//{"airportResponse":{"$":{"authorisedAPI":"true","processingDurationMillis":"119","success":"true"},"airports":[{"airports":[{"$":{"city":"Tel-aviv","country":"Israel","lat":"32.011389","lng":"34.886667","name":"Ben Gurion","timezone":"Asia/Jerusalem"},"code":["TLV"]}]}]}}
};
when the $q.all executes the promises (airportPromises) I expected that printing the table object will come after the promises are ready, but actually the table printed before the promise have answer:
$q.all(airportPromises).finally(function(res){
//return callback(null, table);
console.log(table);
},function(err){
callback(err);
return console.error('one promise error',err);
})
Here all the code:
this.addAirportsToTable = function (table,callback) {
var airportPromises = [];
// var defered = $q.defer();
this.whenFlightNeeded(table).then(function (result) {
var table = result;
for (let dayIndex = 0; dayIndex < table.length; dayIndex++) {
if (table[dayIndex].flight.flight) {
var origin = {
maxAirports: 3,
lat: table[dayIndex]['cityGoogleInf'][0].latitude,
lng: table[dayIndex]['cityGoogleInf'][0].longitude
};
var dist = {
maxAirports: 3,
lat: table[dayIndex + 1]['cityGoogleInf'][0].latitude,
lng: table[dayIndex + 1]['cityGoogleInf'][0].longitude
};
var promise1 = flightAPIService.getNearestAirports(origin).then(function (resultOriginAirport) {
table[dayIndex]['flight'].airport.push(resultOriginAirport.data);
});
var promise2 = flightAPIService.getNearestAirports(dist).then(function (resultDistAirport) {
table[dayIndex + 1]['flight'].airport.push(resultDistAirport.data);
});
airportPromises.concat([promise1,promise2]);
}
}
$q.all(airportPromises).finally(function(res){
//return callback(null, table);
console.log(table);
},function(err){
callback(err);
return console.error('one promise error',err);
})
});
//return defered.promise;
}
Any idea how to make sure that all promises are done then to print the table?
in this screenshot we can see that the object table was printed then the debugger goes back again to finish the promise task:
I believe the issue is your concat.
concat does not modify the existing array but returns a new one. So your calling $q.all passing it an empty array therefore is resolves instantly.
From the MDN:
Note: Concatenating array(s)/value(s) will leave the originals untouched. Furthermore, any operation on the new array will have no effect on the original arrays, and vice versa.
Quick example: https://jsfiddle.net/bunc6zg9/
On a side note $q.all resolves with an array of the values of the promises its waiting on. So if you returned your table values in the .then's from your two promises you can build your table in the $.all's then. That way you don't have a variable global to your promise chain containing its state.
I'm trying to learn how to use promises with arrays and some async mongo queries. Here's the method that I currently have, but Q.allSettled executes before my mongo queries b/c nothing has been pushed to the array yet that Q.allSettled is looking at.
How can I modify this method so that all my async queries are executed before Q.allSettled.spread executes?
function buildCaseObject() {
var returnPromise = Q.defer();
var promises = [];
var allObjects = [];
var subjects = rdb.collection('subjects');
var observations = rdb.collection('observation');
// Loop through all subjects from current subject list
subjects.find({'player._id': {$elemMatch: {root: '1.2.3.99.100.2', extension: {$in : subjectList}}}}).each(function(err, subject) {
var def = Q.defer();
promises.push(def);
if (err) {
def.reject(err);
} else if (subject== null) {
return def.resolve();
}
var caseObject = {};
caseObject.subject= clone(subject);
// Add observations to the subject
observations.find({subjectId: subject._id}).toArray(function(err, allObs) {
if (err) {
def.reject(err);
}
caseObject.observations = clone(allObs);
allObjects.push(caseObject);
def.resolve();
});
});
Q.allSettled(promises).then(function() {
// GETTING IN HERE BEFORE GETTING INTO THE CALLBACK OF subjects.find.
// THEREFORE THE ARRAY IS EMPTY
console.log('in spread');
console.log(allObjects.length);
returnPromise.resolve(allObjects);
}).fail(function(err) {
returnPromise.reject(err);
});
return returnPromise.promise;
}
Two things:
Q.allSettled will only capture the promises that are in the array at the time it is called.
You will need to wait until you have populated the array, perhaps with a promise for the completion of the each call above.
The other is that Q.defer() returns a {promise, resolve} pair. You will need to add only the promise to the promises array.
promises.push(def.promise);
I think this is a really stupid question but I'm having a hard time wrapping my head around promises.
I'm using Q (for nodejs) to sync up a couple of async functions.
This works like a charm.
var first = function () {
var d = Q.defer();
fs.readdir(path,function(err,files){
if(err) console.log(err);
d.resolve(files);
});
return d.promise;
};
var second = function (files) {
var list = new Array;
files.forEach(function(value, index){
var d = Q.defer();
console.log('looking for item in db', value);
db.query(
'SELECT * FROM test WHERE local_name =? ', [value],{
local_name : String,
},
function(rows) {
if (typeof rows !== 'undefined' && rows.length > 0){
console.log('found item!', rows[0].local_name);
d.resolve(rows[0]);
} else {
var itemRequest = value;
getItemData(itemRequest);
}
}
);
list.push(d.promise);
});
return Q.all(list);
};
first()
.then(second)
.done(function(list){
res.send(list);
});
The problem I have is with this little function:
getItemData(itemRequest)
This function is filled with several of callbacks. The promise chain runs through the function just fine but ignores all the callbacks I use ( eg several XHR calls I make in the function).
A simplified version of the function looks like this (just to give you an idea):
function getItemData(itemRequest){
helper.xhrCall("call", function(response) {
var requestResponse = JSON.parse(response)
, requestInitialDetails = requestResponse.results[0];
downloadCache(requestInitialDetails,function(image) {
image = localImageDir+requestInitialDetails.image;
helper.xhrCall("call2", function(response) {
writeData(item,image,type, function(){
loadData(item);
});
});
} else {
writeData(item,image,type, function(){
loadData(item);
});
}
});
});
The xhr function I use looks like this:
xhrCall: function (url,callback) {
var request = require("request")
, colors = require('colors');
request({
url: url,
headers: {"Accept": "application/json"},
method: "GET"
}, function (error, response, body) {
if(!error){
callback(body);
}else{
console.log('Helper: XHR Error',error .red);
}
});
}
So my questions:
Can I leave the function unaltered and use the callbacks that are in place ánd the promise chain?
Or do I have to rewrite the function to use promises for the XHR?
And if so, How can I best write my promise chain? Should I reject the initial promise in the forEach?
Again, sorry if this is a really stupid question but I don't know what the right course of action is here.
Thanks!
[EDIT] Q.nfcall, I don't get it
So I've been looking into Q.nfcall which allows me to use node callbacks. Bu I just don't understand exacly how this works.
Could someone give a simple example how I would go about using it for a function with several async xhr calls?
I tried this but as you can see I don't really understand what I'm doing:
var second = Q.nfcall(second);
function second (files) {
[EDIT 2]
This is the final funcction in my getitemdata function callback chain. This function basically does the same as the function 'second' but I push the result directly and then return the promise. This works as stated, but without all the additional callback data, because it does not wait for the callbacks to return with any data.
function loadData(item) {
var d = Q.defer();
db.query(
'SELECT * FROM test WHERE local_name =? ', [item],{
local_name : String,
},
function(rows) {
if (typeof rows !== 'undefined' && rows.length > 0){
list.push(d.promise);
}
}
);
});
return Q.all(list);
};
Your answer is not really clear after your second edit.
First, on your orignal question, your getItemData has no influence on the promise chain.
You could change you the function's call signature and pass your deferred promise like so.
getItemData(itemRequest, d)
and pass this deferred promises all the way to your xhrCall and resolve there.
I would re-write your whole implementation and make sure all your functions return promises instead.
Many consider deferred promises as an anti-pattern. So I use use the Promise API defined in harmony (the next javascript)
After said that, I would re-implement your original code like so (I've not tested)
var Promise = Promise || require('es6-promise').Promise // a polyfill
;
function errHandler (err){
throw err
}
function makeQuery () {
var queryStr = 'SELECT * FROM test WHERE local_name =? '
, queryOpt = {local_name: String}
;
console.log('looking for item in db', value)
return new Promise(function(resolve, reject){
db.query(queryStr, [value], queryOpt, function(rows) {
if (typeof rows !== 'undefined' && rows.length > 0){
console.log('found item!', rows[0].local_name);
resolve(rows[0]);
} else {
// note that it returns a promise now.
getItemData(value).then(resolve).catch(errHandler)
}
})
})
}
function first () {
return new Promise(function(resolve, reject){
fs.readdir(path, function(err, files){
if (err) return reject(err)
resolve(files)
})
})
}
function second (files) {
return Promise.all(files.map(function(value){
return makeQuery(value)
});
}
first()
.then(second)
.then(res.send)
.catch(errHandler)
Note that there is no done method on the Promise API.
One down side of the new Promise API is error handling. Take a look at bluebird.
It is a robust promise library which is compatible with the new promise API and has many of the Q helper functions.
As far as I can tell, you need to return a promise from getItemData. Use Q.defer() as you do in second(), and resolve it when the callbacks complete with the data. You can then push that into list.
To save code, you can use Q.nfcall to immediately call a node-style-callback function, and return a promise instead. See the example in the API docs: https://github.com/kriskowal/q/wiki/API-Reference#qnfcallfunc-args
With promise API, how to send two asynchronous request in parallel, and resolve the combined result as the response.
var get = function(id){
var res1, res2;
var deferred = $q.defer();
Db.get(id, "abc")
.then(function (d) {
//deferred.resolve(d));
res1 = d;
}, function (e) {
//error
});
Db.get(id, "def")
.then(function (d) {
//deferred.resolve(d));
res2 = d;
}, function (e) {
//error
});
//?????? how to return {res1:res1 , res2: res2}
return deferred.promise;
};
now, when I call get() like
get(123).then(function(d)){
// d= {res1: res1, res2: res2}
},
...
I need to get the combined result as indicated. How to do this with Angular promise API?
As #Matt said, you need to use $q.all, but the usage isn't quite right. AngularJS doesn't support .done and .fail and they don't work quite like that anyway in that there's no such thing as a promise for multiple values, instead you just have a promise for an array.
If you were writing this using the full Q we would write:
var get = function (id) {
return Q.all([Db.get(id, "abc"), Db.get(id, "def")])
.spread(function (res1, res2) {
return {res1: res1, res2: res2};
});//the error case is handled automatically
};
In this case, .spread acts like .then except that it spreads the results of an array for a promise out over the arguments of its onFulfilled function. To change this to use the promise methods from AngularJS we just need to make do without .spread. This leads to the following solution:
var get = function (id) {
return $q.all([Db.get(id, "abc"), Db.get(id, "def")])
.then(function (res) {
return {res1: res[0], res2: res[1]};
});//the error case is handled automatically
};
The beauty of this is that we are freed from handling all the nitty grity of error propagation and storing the partial results because .then acts as a filter. If you leave out the error handler, it automatically propagates any errors. This means that if either of the input promises are rejected, the result will be rejected. If both promises are fulfilled successfully, res is the array of those resolution values.
I have something to add to #ForbesLindesay answer.
In our case, we wanted partial results: if a request failed (eg. server has an hiccup, we request something deleted by somebody else, etc.), we still want to collect the valid responses, and to report the errors.
I found out that we need to handle success and failure on each promise, returning a value that will be collected by $q.all.
Here is our code, simplified and made generic ('item'...):
var promiseList = _.map(itemList, function(item)
{
return DataService.getISubtems(item.id)
.then(
function(response)
{
var subItems = response.data;
$log.info('Received sub-item list;' + subItems.length + ';items received');
return subItems;
},
function(reason)
{
$log.warn('Sub-item list not received for item;' + item.name + ';' + item.id);
$scope.errorList.push('Sub-item list not received for item "' + item.name + '"');
}
);
});
$q.all(promiseList)
.then(function(itemArray)
{
// We get an array of arrays interleaved with undefined value when an error was raised.
// That's because error handling doesn't return anything, ie. returns undefined.
// We remove these undefined values then put all operations at the same level.
var allOperations = _(operationArray).reject(_.isUndefined).flatten().value();
if ($scope.errorList.length > 0)
{
NotificationService.warning('Items Fetching', 'Errors while getting item list:\n' +
$scope.errorList.join('\n'));
}
$scope._onItemListReceived(allItems);
});
Note that we use Lodash (_.map, _.flatten, _.reject, _.isUndefined) but I think the usage is quite clear (that's the nice point of this library!).
You can use the angular-q-spread library and then use the same code as #ForbesLindesay's first example:
// The module needs $q-spread as a dependency:
// angular.module('…', ['$q-spread']);
var get = function (id) {
return $q.all([Db.get(id, "abc"), Db.get(id, "def")])
.spread(function (res1, res2) {
return {res1: res1, res2: res2};
});
};