I'm facing an issue to return promises using $q#all method.
I want to make promises dependent on each other, i.e.:
If I set obj1, obj2 and obj3 I want to get them in the same order.
How can I achieve this?
Factory:
mainFactory.$inject = ['$http', '$q'];
function mainFactory($http, $q) {
var mainFactory = {
getPromises: getPromises
};
return mainFactory;
function getPromises(id) {
promises = {
'obj1': $http.get('http1'),
'obj2': $http.get('http2'),
'obj3': $http.get('http3'),
'obj4': $http.get('http4', { params: { 'id': id } }),
'obj5': $http.get('http5'),
'obj6': $http.get('http6', { params: { 'id': id } })
};
return $q.all(promises);
}
}
Controller:
MainCtrl.$inject = ['mainFactory'];
function MainCtrl(mainFactory) {
var vm = this;
mainFactory.getPromises(id)
.then(getResponse)
.catch(getError);
function getResponse(response) {
var keys = Object.keys(response), i = keys.length;
while (i--) {
var key = keys[i];
console.log(key); // I want all the keys in order, i.e. => obj1, obj2.. and so on
var value = response[key].data;
switch(key) {
...
}
}
}
function getError(error) {
console.log(error);
}
}
EDIT:
I tried this way also:
var promises = {};
return $http.get('/admin/http1.json').then(function (value) {
promises['obj1'] = value;
})
.then(function (result) {
return $http.get('/admin/http2.json').then(function (value) {
promises['obj2'] = value;
});
}).then(function (result) {
return $http.get('/admin/http3.json').then(function (value) {
promises['obj3'] = value;
});
});
return $q.all(promises);
Using $q.all will resolve each promise in no particular order. If you want them to execute after each promise has been resolve, use promise chaining.
function getPromises(id) {
var getObjA = function () {
return $http.get('http1');
};
var getObjB = function () {
return $http.get('http2');
};
var getObjC = function () {
return $http.get('http3');
};
var getObjD = function () {
return $http.get('http4', { params: { 'id': id } });
};
var getObjE = function () {
return $http.get('http5');
};
var getObjF = function () {
return $http.get('http5', { params: { 'id': id } });
};
return getObjA()
.then(getObjB)
.then(getObjC)
.then(getObjD)
.then(getObjE)
.then(getObjF);
}
Edit: as an additional info, you can catch any error in any of those promise by placing a catch statement here
getPromises("id")
.then(<success callback here>)
.catch(<error callback that will catch error on any of the promises>);
Meaning, once a promise fails, all the succeeding promises below wouldn't be executed and will be caught by your catch statement
Edit 2
Mistake, I just copied you code above without realizing it was an object. LOL.
promises = [
$http.get('http1'),
$http.get('http2'),
$http.get('http3'),
$http.get('http4', { params: { 'id': id } }),
$http.get('http5'),
$http.get('http6', { params: { 'id': id } })
]
Edit 1
Sorry I didn't notice the comments Jared Smith is correct.
Object keys are inherently unordered. Use an array instead.
Edit 0
Object keys wont be ordered. Use array on declaring your promises.
promises = [
$http.get('http1'),
$http.get('http2'),
$http.get('http3'),
$http.get('http4', { params: { 'id': id } }),
$http.get('http5'),
$http.get('http6', { params: { 'id': id } })
]
$q.all(promises)
.then(functions(resolves){
// resolves here is an array
}).catch(function(err){
// throw err
});
Related
I have a problem with promise
I want to get all Category and sub Category ( Multi level Category) using promise. But i can't get multi level promise. I can get first Promise use Promise.all(Array) but can't get child promise.
getChild: function(_cats){
var arrCat=[];
var that = this;
for (var i = 0, len = _cats.length; i < len; i++) {
var t = new Promise(function(resolve,reject){
var a = [];
var _c = _cats[i];
_cats[i].getChildren(function(err, _childs){
a.push({
root: _c,
child: that.getChild(_childs)
});
resolve(a);
})
})
arrCat.push (t);
}
return Promise.all(arrCat);
and other function to call and respon api:
this.getChild(_cats).then(_r => {
return res.ok(_r);
})
and the respone
[{
root: {value} // it's ok
child: promise(pending) //it's problem
}]
Please help me! thank you!
First extract promisified version of cat.getChildren to make code clear. Then map over all categories and for each category return a Promise that resolves only after every nested child is loaded by recursively calling getChild. Something like this should do the trick
function getChildren(cat) {
new Promise(function(resolve) {
cat.getChildren(function(_, children) {
resolve(children)
})
})
}
getChild: function getChild(_cats) {
return Promise.all(
_cats.map(function(cat) {
return getChildren(cat)
.then(getChild) // load each child
.then(function(children) {
return {
root: cat,
children: children
}
})
})
)
}
This code work perfect. Thanks to Yury
function getChildren(cat) {
new Promise(function(resolve) {
cat.getChildren(function(_, children) {
resolve(children)
})
})
}
getChild: function getChild(_cats) {
return Promise.all(
_cats.map(function(cat) {
return getChildren(cat)
.then(getChild) // load each child
.then(function(children) {
return {
root: cat,
children: children
}
})
})
)
}
I come from java/python background and new to javascript. I need to create a product list with the description of its children as well included in a jsonarray.
parent_list:
[{ children: [ 100714813, 100712694 ],
sp: '89.10',
weight: '1 ltr',
pack_type: 'Carton',
brand: 'Real',
p_desc: 'Fruit Power Juice - Orange' }]
Now for every parent I need to again iteratively fetch the children details by connecting to the database and finally have the result consolidated in a single jsonarray. But when I execute the below code, the control doesn't wait for fetching the children data( which makes sense as its being called asynchronously!), the result I get is a jsonarray that contains data only for the parents that have no children.
exports.productDetailsQuery = function(options) {
var AEROSPIKE_NAMESPACE = '';
var AEROSPIKE_SET = 'products';
var PD_KEY_VERSION_NUMBER = '1';
var defer = sails.Q.defer();
var results = options.results;
var parent_list = [];
var finalData = [];
var productKeys = results.map(
function(x){
return {
ns: AEROSPIKE_NAMESPACE,
set: AEROSPIKE_SET,
key: "pd.v" + PD_KEY_VERSION_NUMBER + '.' + 'c' + options.city_id + '.' + x.sku.toString()
}
}
);
var status = require('aerospike').status;
var breakException = {};
// Read the batch of products.
sails.aerospike.batchGet(productKeys, function (err, results) {
if (err.code === status.AEROSPIKE_OK) {
for (var i = 0; i < results.length; i++) {
switch (results[i].status) {
case status.AEROSPIKE_OK:
parent_list.push(results[i].record);
break;
case status.AEROSPIKE_ERR_RECORD_NOT_FOUND:
console.log("NOT_FOUND - ", results[i].keys);
break;
default:
console.log("ERR - %d - ", results[i].status, results[i].keys);
}
}
parent_list.forEach(function(parent){
var children = parent['children'];
console.log(children)
if(children){
var childKeys = children.map(function(child){
return {
ns: AEROSPIKE_NAMESPACE,
set: AEROSPIKE_SET,
key: "pd.v" + PD_KEY_VERSION_NUMBER + '.' + 'c' + options.city_id + '.' + child.toString()
}
});
sails.aerospike.batchGet(childKeys, function(err, childData){
if(err.code === status.AEROSPIKE_OK){
console.log('this called')
var entry = {};
entry['primary_prod'] = parent;
entry['variants'] = childData;
finalData.push(entry);
}
});
}
else{
var entry = {};
entry['primary_prod'] = parent;
finalData.push(entry);
}
});
defer.resolve(finalData);
} else {
defer.reject(err);
}
});
return defer.promise;
}
I need finalData to be like:
[{"primary_prod":{ children: [ 100714813, 100712694 ],
sp: '89.10',
weight: '1 ltr',
pack_type: 'Carton',
brand: 'Real',
p_desc: 'Fruit Power Juice - Orange' },
"variants":[{child_data},{child_data}]}, ...........]
Would really appreciate any help as to how to make it work.Is there a specific pattern to handle such cases?
Thanks!
What you have written is along the right lines but only the outer batchGet() is promisified. Because there's no attempt to promisify the inner batchGet(), it doesn't contribute to the finally returned promise.
Your overall pattern might be something like this ...
exports.productDetailsQuery = function(options) {
return sails.aerospike.batchGetAsync(...).then(results) {
var promises = results.filter(function(res) {
// Filter out any results that are not `AEROSPIKE_OK`
...
}).map(function(parent) {
// Map the filtered results to an array of promises
return sails.aerospike.batchGetAsync(...).then(function(childData) {
...
});
});
// Aggregate the array of promises into a single promise that will resolve when all the individual promises resolve, or will reject if any one of the individual promises rejects.
return sails.Q.all(promises);
});
}
... where batchGetAsync() is a promisified version of batchGet().
The fully fleshed-out the code will be longer but can be kept reasonably concise, and readable, by first defining a couple of utility functions. You might end up with something like this :
// utility function for making a "key" object
function makeKey(obj) {
return {
ns: '', //AEROSPIKE_NAMESPACE
set: 'products', //AEROSPIKE_SET
key: 'pd.v1.c' + options.city_id + '.' + obj.toString()
}
}
// promisified version of batchGet()
function batchGetAsync(obj) {
var defer = sails.Q.defer();
batchGet(obj, function(err, results) {
if(err.code === status.AEROSPIKE_OK) {
defer.resolve(results);
} else {
defer.reject(err);
}
});
return defer.promise;
}
var status = require('aerospike').status;
// Main routine
exports.productDetailsQuery = function(options) {
return batchGetAsync(options.results.map(makeKey)).then(results) {
var promises = results.filter(function(res) {
if (res.status === status.AEROSPIKE_OK) {
return true;
} else if(status.AEROSPIKE_ERR_RECORD_NOT_FOUND) {
console.log("NOT_FOUND - ", res.keys);
} else {
console.log("ERR - %d - ", res.status, res.keys);
}
return false;
}).map(function(parent) {
var entry = { 'primary_prod': parent },
children = parent['children'];
if(children) {
return batchGetAsync(children.map(makeKey)).then(function(childData) {
entry.variants = childData;
return entry;
});
} else {
return entry;
}
});
return sails.Q.all(promises);
});
}
With the new ES6 plus async stuff and babel its simpler. You can npm i -g babel npm i babel-runtime then compile and run the following with babel test.js --optional runtime --stage 2 | node:
import {inspect} from 'util';
let testData = [
{ id: 0, childIds: [1,2]},
{ id: 1, childIds:[] },
{ id: 2, childIds:[] }
];
function dbGet(ids) {
return new Promise( r=> {
r(ids.map((id) => { return testData[id];}));
});
}
async function getChildren(par) {
let children = await dbGet(par.childIds);
par.children = children;
}
async function getAll(parentIds) {
let parents = await dbGet(parentIds);
for (let p of parents) {
await getChildren(p);
}
return parents;
}
async function test() {
var results = await getAll([0]);
console.log(inspect(results,{depth:3}));
}
test().then(f=>{}).catch( e=> {console.log('e',e)});
I want the data in res passed to my notes variable. But it's returning a bigger nested object. Why is it happening?
If I inspect in the console the value of cleanArrayOfNotes I get the object that I want, but once its assigned to notes it becomes a quite bigger object. I understand it's part of the nature of the Promises, which at the moment I still trying to understand. Any help?
notes_service.js
var notesService = {notesObjectInService: [], newNote: null};
notesService.getAll = function() {
return $http.get('/notes.json').success(function(data){
//console.log(data)
angular.copy(data, notesService.notesObjectInService);
//console.log(notesService)
})
};
navCtrl.js
var notes = notesService.getAll().then(function(res){
var cleanArrayOfNotes = res.data;
//navCtrl line12
console.log(cleanArrayOfNotes);
return cleanArrayOfNotes;
});
//navCtrl line16
console.log(notes);
This should work for you:
notes_service.js
app.factory ('NoteService', function($http) {
return {
getAll: function() {
return $http.get('/notes.json').then(function(response) {
return response.data;
});
}
}
});
navCtrl.js
NotesService.getAll().then(function(res){
$scope.cleanArrayOfNotes = res.data;
});
Or, if you want to return the result rather than the promise, you can:
notes_service.js
app.factory ('NoteService', function($http) {
var notes = [];
var promise = $http.get('/notes.json').then(function(response) {
angular.copy(response.data, notes);
return notes;
});
return {
getAll: function() {
return notes;
},
promise: promise
}
});
navCtrl.js
// get array of notes
scope.cleanArrayOfNotes = NotesService.getAll();
// or use promise
NoteService.promise.then(function(response) {
scope.cleanArrayOfNotes = response.data;
});
I have a long chain of promises that wind through a module of my code. I don't know beforehand how many promises I'll wind through, nor do I have scope from any one promise to any other other promise (meaning I can't do a Promise.join()).
My problem is that I have then() callbacks attached to multiple promises on this chain. How can I control which one is fired last?
UPDATE: Here's a simplified example:
var aFn = function () {
return bFn().then(someFn);
};
var bFn = function () {
return new Promise(function (resolve, reject) {
if (a) return resolve();
else return reject();
});
};
aFn().then(anotherFn);
My problem is that the .then() in aFn().then(anotherFn) gets called before bFn().then(someFn).
Here are a few code snippets that helps illustrate my issue:
strategy.js
execute: function (model, options) {
options.url = this.policy.getUrl(model, this.method, options);
options.collection = this.policy.getCollection(model, options);
options.model = model;
// This is a Promise that eventually calls get()
return this.sync(model, options);
},
get: function (key, options) {
var updateCollection = this._getUpdateCollection(options);
var getFromCache = _.bind(this.store.get, this.store, key, options);
if (updateCollection) {
// updateCollection received a promise from store-helpers.js:proxyGetItem()
return updateCollection().then(
function (collection) {
var modelResponse = collection.policy.findSameModel(collection.raw, options.model);
return modelResponse ? modelResponse : getFromCache();
},
getFromCache
);
} else {
return getFromCache();
}
},
_getUpdateCollection: function (options) {
var collection = options && options.collection;
var collectionControl = collection && collection.sync && collection.sync.hoardControl;
if (collection && collectionControl) {
var collectionKey = collectionControl.policy.getKey(collection, options);
return _.bind(function () {
// store.get() passes the returned promise of store-helpers.js:proxyGetItem()
return this.store.get(collectionKey, options).then(function (rawCollection) {
return {
control: collectionControl,
policy: collectionControl.policy,
key: collectionKey,
raw: rawCollection
};
});
}, this);
}
},
store.js
// Just a proxy function that wraps a synchronous get
get: function (key, options) {
return this.getItem.apply(this, arguments);
},
getItem: StoreHelpers.proxyGetItem
store-helpers.js
proxyGetItem: function (key, options) {
return Hoard.Promise.resolve()
.then(_.bind(function () {
return this.backend.getItem(key);
}, this))
.then(function (raw) {
var storedValue = JSON.parse(raw);
if (storedValue !== null) {
return storedValue;
} else {
return Hoard.Promise.reject();
}
});
},
In a very different part of the app I also have:
var originalExecute = Hoard.Strategy.prototype.execute;
Hoard.Strategy.prototype.execute = function (model, options) {
options.originalOptions = _.clone(options);
if (options.saveToCacheFirst)
return originalExecute.call(this, model, options)
.then(_.result(options.originalOptions, 'success'), _.result(options.originalOptions, 'error'));
return originalExecute.call(this, model, options);
}
I would like for the .then() above to fire last, however when .resolve in store-helpers.js is fired, this last .then() callback is invoked.
My problem is that the .then() in aFn().then(anotherFn) gets called before bFn().then(someFn)
No it doesn't. As you've written the example, that expression is equivalent to
bFn().then(someFn).then(anotherFn)
- and while the .then() method does get called before someFn, the anotherFn callback does not.
I was trying to reduce my promise objects in my service. I have something like
angular.module('myApp').service('testService', ['Employees','$q',
function(Employees, $q) {
var service = {};
var firstEmp;
var deferred = $q.defer();
Employees.query({
Id: 123
}, function(objects) {
firstEmp = objects[0];
deferred.resolve(objects);
})
service.getFirstEmployee = function() {
var deferredtwo = $q.defer();
// return deferredtwo.promise;
//How to solve the double promise defer in my case
//First promise is wait for the whole employees
//this one is to return first employee
deferred.promise.then(function(){
deferredtwo.resolve(firstEmp);
})
return deferredtwo.promise;
}
return service;
]);
Controller
testService.getFirstEmployee.then(function(firstEmployee){
console.log(firstEmployee) <---show first employee
})
I am not sure how to resolve the double promise objects. Can anyone help me? Thanks a lot!
If your ultimate objective is just to get the first employee, then you don't need all this "double promise" stuff at all. Just resolve one promise with the first employee:
angular.module('myApp').service('testService', ['Employees','$q',
function(Employees, $q) {
var pFirstEmployee = $q(function (resolve) {
Employees.query({ Id: 123 }, function(objects) {
resolve(objects[0]);
});
});
return {
getFirstEmployee: function() {
return pFirstEmployee;
}
};
}
]);
If you want two methods - one that returns a promise for all employees returned from the query, and another that returns just the first one from that set, just create a promise for all the employees and chain off of that:
angular.module('myApp').service('testService', ['Employees','$q',
function(Employees, $q) {
var pAllEmployees = $q(function (resolve) {
Employees.query({ Id: 123 }, resolve);
}),
pFirstEmployee = pAllEmployees.then(function (employees) {
return employees[0];
});
return {
getAllEmployees: function() {
return pAllEmployees;
},
getFirstEmployee: function() {
return pFirstEmployee;
}
};
}
]);
After the clarification I guess this is what you want:
angular.module('myApp').service('testService', ['Employees','$q',
function(Employees, $q) {
var service = {};
var deferred = $q.defer();
Employees.query({Id: 123}, function(objects) {
firstEmp = objects[0];
deferred.resolve(objects);
});
service.getFirstEmployee = function() {
return deferred.promise.then(function(employees){
return employees[0];
});
}
return service;
]);