I'm trying to filter documents inside an array. I have a asynchronous getter function that gets a user, and inside that i should return the document if the user is the same as the current session user.
The problem is that the return function doesn't work inside that getter, since it returns a promise. I don't understand Synch and Async very well but if i put it outside like this:
documents = documents.filter(document => {
if(document.user.id != this.user.id) {
return document
}
})
It returns just fine
documents = documents.filter(document => {
if(document.user.id != this.user.id) {
getter.getNextUserToSign(document.id).then(res => {
if( res != null) {
//if next user == session user
if(res.id == this.user.id){
/*This log shows*/
console.log("is equal to " + res.id)
/*but this return doesnt work*/
return document
}else{
console.log(res.id + " is not equal")
}
}else{
console.log("is null")
}
})
}
})
It goes until the log saying "is equal to {id}" but the document is not returned.
As #JClassic said, you're returning from the inner anonymous function, not the main function. If getNextUserToSign is async and returns a promise, as it looks like it does, then the filter (which is synchronous) will continue iterating even before the first call to that is complete.
What you can do instead is a bit of refactoring. Instead of directly filtering your document array, you can map it onto those getNextUserToSign promises, and then use Promise.all() to wait for all of those promises to resolve (i.e. complete). The .then callback on the Promise.all will be passed an array that's basically your res values for each of the documents in the same order, and then you can filter the main array based on its corresponding values in the results array.
So a refactor like that might look something like this (I took out your console.logs for clarity, you can put them back if you want them):
documents = documents.filter(document => document.user.id !== this.user.id);
const promises = documents.map(document => getter.getNextUserToSign(document.id));
Promise.all(promises).then(results => {
documents = documents.filter((document, i) => {
return results[i] && results[i].id === this.$session.get('user').id;
});
});
So first it filters out the documents that have the current user's ID, since that doesn't require async. Then it maps the remainders to their promises. Then, when all the promises have resolved, it filters the documents array based on the results from those promises.
This is not a scoping issue but more of a trying to handle an asynchronous operation in synchronous manner.
You are using a getter which returns a promise which is an asynchronous operation and while you're expecting your getter to return you your document, it returns a promise instead.
In order to solve it you can use a combination of async/await and promises to resolve your document, something like this probably!
documents = documents.filter(async document => {
return await new Promise((resolve, reject) => {
if (document.user.id !== this.user.id) {
getter.getNextUserToSign(document.id).then(res => {
if (res !== null) {
if (res.id == this.$session.get("user").id) {
resolve(document);
}
}
reject(res.id + " is not equal");
});
}
});
});
Related
I'm currently fetching data from an API and I need to do multiple GET requests (using axios). After all those GET requests are completed, I return a resolved promise.
However, I need to do these GET requests automatically based on an array list:
function do_api_get_requests() {
return promise = new Promise(function(resolve, reject) {
API_IDs = [0, 1, 2];
axios.get('https://my.api.com/' + API_IDs[0])
.then(data => {
// Do something with data
axios.get('https://my.api.com/' + API_IDs[1])
.then(data => {
// Do something with data
axios.get('https://my.api.com/' + API_IDs[2])
.then(data => {
// Do something with data
// Finished, resolve
resolve("success");
}
}
}
}
}
This works but the problem is API_IDs isn't always going to be the same array, it will change. So I'm not sure how to chain these requests automatically.
Since you said it may be a variable length array and you show sequencing the requests, you can just loop through the array using async/await:
async function do_api_get_requests(API_IDS) {
for (let id of API_IDS) {
const data = await axios.get(`https://my.api.com/${id}`);
// do something with data here
}
return "success";
}
And, since you said the list of API ids would be variable, I made it a parameter that you can pass into the function.
If you wanted to run all the API requests in parallel (which might be OK for a small array, but might be trouble for a large array) and you don't need to run them in a specific order, you can do this:
function do_api_get_requests(API_IDS) {
return Promise.all(API_IDS.map(async (id) => {
const data = await axios.get(`https://my.api.com/${id}`);
// do something with data here for this request
})).then(() => {
// make resolved value be "success"
return "success";
});
}
Depending upon your circumstances, you could also use Promise.allSettled(). Since you don't show getting results back, it's not clear whether that would be useful or not.
You can use Promise.all() method to do all API requests at the same time, and resolve when all of them resolves.
function do_api_get_requests() {
const API_IDs = [0, 1, 2];
let promises = [];
for (const id of API_IDS) {
promises.push(axios.get(`https://my.api.com/${id}`));
}
return Promise.all(promises);
}
If you use Bluebird.js (a better promise library, and faster than the in-built Promise), you can use Promise.each(), Promise.mapSeries(), or Promisme.reduce() to do what you want.
http://bluebirdjs.com
I'm runningif-else block of code as shown below :
if (condition){
var id = data1.id ;
}
else
var id = data2.id ;
And i'm using this id for some other operation as :
if (id==some value){
do something ;
}
The above operation is performed when a user registers to the application or if the already logged in user wants views a certain page. Since, node.js is asynchronous, when the user register and the result is stored in database, the id field is undefined and in the 2ndif condition the value of id becomes undefined. But if the user is already logged in this works perfectly as there is not much time consuming operation done. So can anyone suggest me some way to resolve the above issue. Any help or guidance will be highly appreciated.
If any part of your function or if statement is asynchronous, then you need to design the whole function to be asynchronous and return a result either via a callback or via a returned promise. Then, even if the if statement doesn't need to do something asynchronous, you still return the value asynchronously so the caller always gets the response the same way regardless of how it was retrieved. Promises are great for this.
Here's a typical function that handles either a synchronous result or an asynchronous result by designing for an asynchronous result by returning a promise that resolves with the value.
let cache = {};
function getValue(key) {
// check cache to see if we already have the value
let val = cache[key];
if (val !== undefined) {
// already have value, return promise that resolves with this value
return Promise.resolve(val);
} else {
return new Promise(function(resolve, reject) {
db.getValue(key, function(err, val) {
if (err) {
reject(err);
} else {
// save value in cache
cache[key] = val;
resolve(val);
}
});
});
}
}
getValue(someKey).then(function(val) {
// use val here whether it was in the cache or not
}).catch(function(err) {
// handle error here
});
This my code :
Parse.Cloud.job("deleteDuplicatedMates", function(request, status) {
var friendshipQuery = new Parse.Query("Friendship");
friendshipQuery.each((friendship) => {
var innerQuery1 = new Parse.Query("Friendship");
innerQuery1.equalTo("user1", friendship.get("user1"));
innerQuery1.equalTo("user2", friendship.get("user2"));
var innerQuery2 = new Parse.Query("Friendship");
innerQuery2.equalTo("user1", friendship.get("user2"));
innerQuery2.equalTo("user2", friendship.get("user1"));
var findPS = Parse.Query.or(innerQuery1, innerQuery2)
.notEqualTo("objectId", friendship.id)
.find()
.then(function(objects) {
console.log("did found");
if (objects.length > 0) {
//delete deplicated objects
Parse.Object.destroyAll(objects);
}
})
return Parse.Promise.when(findPS);
}).then(function() {
status.success("I just finished");
}, function(error) {
status.error("There was an error");
})
});
My code works fine but I need to update it so that :
The next each(friendship) is treated only if the current one has finished deleting the found object, so the flow will be like this :
get first object => find duplicated objects => delete found objects => get the second object => find duplicated objects => delete them => get the this one ...
If a promise block reaches the end of the block, and hasn't returned another promise, it automatically returns an empty resolved promise. So, I would change .find() to return findPs.find().../*rest of code there*/
Assuming
Parse.Object.destroyAll(objects);
is asynchronous, simply returning the destruction promise should do the trick.
return Parse.Object.destroyAll(objects);
It's also hard to help you for real, given how little context you've given.
I have the following function, my goal is to push to the items list, when the components can identify their parent item.
The problem that I have is that when I am pushing to the list console.log() shows me that the object is in there, but when I return the list and catch it in another function, there is nothing in the list.
I think that the items list is returned before the code above it is done.
private get_items_for_request() {
return this.Item.forRequest(this.request.id, ['group'])
.then((_items) => {
var items = [];
for (var item of _items) {
return this.ItemComponent.forItem(item.id, ['type'])
.then((_components) => {
for (var component of _components) {
if (component.type.can_identify_item) {
items.push({
group_id: item.group.reference,
identifier_code: this.remove_check_digit_if_necessary(
component.identifier_code),
quantity: 1
});
break;
}
}
}, (reason) => {
this.Toast.error(
this.gettextCatalog.getString('components.load_failed'));
return [];
});
}
return items;
}, (reason) => {
this.Toast.error(
this.gettextCatalog.getString('items.failed_load'));
return [];
});
}
I'm afraid the trouble is with your approach, not with the code itself. A promise is just that - a promise of data some time in future. So, when you function returns (immediately), the promise is not resolved yet and the data your caller function captures is still empty. I don't see console.log() in your code, but would suspect you put it somewhere inside then(), which will be called once data has been received. So you will see data being logged. Trouble is by that time get_items_for_request() has already returned and your caller function has already moved on.
If you want to use a promise, you have to use a callback to be called once promise is resolved. But if you want actual data returned to the caller you need to fetch data synchronously.
For the synchronous fetch, check this response. But beware that synchronous fetch will hurt responsiveness of your script.
For an asynchronous fetch (using promises), you need to define a callback called once all data has been fetched. I won't try to fix your code, but it should go along the following sketch in javascript. Though keep in mind it's just a sketch.
function get_items_for_request(onSuccess) {
var items = []
var total = -1
var sofar = 0;
this.Item.forRequest(this.request.id, ['group'])
.then(function (_items) {
var items = [];
total = _items.length // remember how many nested calls will be made
for (var item of _items) {
this.ItemComponent.forItem(item.id, ['type'])
.then(function (_components) {
// push received data to the items array here
sofar++
if (sofar == total) { // all done
onSuccess(items)
}
}
}
}
}
In the first promise callback, you have two returns, only the first one runs. You return a promise after the first for, which you resolve to undefined. You should wait for an array of promises, each corresponding to a this.ItemComponent.forItem call.
Promise.all helps with this: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
You should do something like this:
return this.Item.forRequest(this.request.id, ['group']).then((_items) => {
return Promise.all(_items.map(function (item) {
return this.ItemComponent.forItem(item.id, ['type']).then((_components) => {
for (var component of _components) {
if (component.type.can_identify_item) {
return {
group_id: item.group.reference,
identifier_code: this.remove_check_digit_if_necessary(
component.identifier_code),
quantity: 1
};
}
}
}, (reason) => {
this.Toast.error(
this.gettextCatalog.getString('components.load_failed'));
return [];
});
}));
}, (reason) => {
this.Toast.error(
this.gettextCatalog.getString('items.failed_load'));
return [];
} )
If you only want one item, find the first non-falsy element in the resulting array
I'm new to javascript promises and am having difficulty using them with a collection of elements. Within the collection, I perform an operation which returns a promise. Once the entire operation (including all the Promises in the collection) has completed, I need to perform another set of operations. The promises within the collection need to go in sequence.
I have tried the following approach:
public cleanup(onCleanupComplete: any): void {
if (this._app == null) return; //this._app comes out of an external API
// Below line of code won't compile, it is just for illustration.
// I'm trying to show that I need a promise in return from method
this.removeConference(0).then(() => {
// Do additional clean up operation and call onCleanupComplete
onCleanupComplete(true, null);
});
}
private removeConference(i : number) {
if (this._app.conversationsManager.conversations == null
|| i === this.conversationLength)
return; // this.conversationLength equals initial
// number of elements in collection
// How do I return a promise here?
var conversation = this._app.conversationsManager.conversations(0);
console.log("app.cleanup.leave", `Leaving conversation ${conversation}`);
conversation.leave().then(() => {
console.log("app.cleanup.leave", `Conversation ${conversation} left successfully`);
this.app.conversationsManager.conversations.remove(conversation);
_ this.removeConference(i);
});
}
What should I return from removeConference once all the conversations in collection are removed?
So this is something that nabbed me early on in understanding promises. You need to get ALL of your code away from the practice of passing in a callback, and then simply using the promise to call that. Instead, to keep the continuous nature of promises, you want to just return promises to the calling function, unless your function is the one that should decide what to do afterward. So, your code should look something like this.
public cleanup(onCleanupComplete: any):Promise<any> {
if (this._app == null) return; //this._app comes out of an external API
// Below line of code won't compile, it is just for illustration.
// I'm trying to show that I need a promise in return from method
var promiseArray = [];
for (var i = 0; i < this.conversationLength; i++) {
promiseArray.push(removeConference(i));
}
return Promise.all(promiseArray);
}
private removeConference(i : number):Promise<any> {
if (this._app.conversationsManager.conversations == null
|| i === this.conversationLength)
return Promise.resolve(true);
var conversation = this._app.conversationsManager.conversations(0);
console.log("app.cleanup.leave", `Leaving conversation ${conversation}`);
return conversation.leave().then(() => {
console.log("app.cleanup.leave", `Conversation ${conversation} left successfully`);
this.app.conversationsManager.conversations.remove(conversation);
this.removeConference(i);
});
}
I'm not 100% sure this compiles correctly, but hopefully it helps conceptually. Promise.all is really the key function here - it takes an array of promises, and creates a matching "control promise" that only resolves when all of them have.