javascript promise still executing code synchronously - javascript

so basically i have a web application that retrieves data from firebase. and since it takes a lot of time to retrieve data from firebase, i used promise to make my code to populate at the right time. here is my code:
var promise = getDataFirebase();
promise.then(function () {
console.log(Collect);
console.log("firsst");
return getDataFirebaseUser();
}).then(function () {
console.log("Second");
});
function getDataFirebase() {
return new Promise(function (resolve, reject) {
refReview.on("value", function (snap) {
var data = snap.val();
for (var key in data) {
Collect.push({
"RevieweeName": data[key].revieweeID.firstname.concat(" ", data[key].revieweeID.lastname),
"ReviewerName": data[key].reviewerID.firstname.concat(" ", data[key].reviewerID.lastname),
rating: data[key].rating,
content: data[key].content,
keyOfReviewee: data[key].revieweeID.userID
})
var getDataToUsers = firebase.database().ref("users").child(data[key].revieweeID.userID);
getDataToUsers.once("value", async function (snap) {
var fnLn = snap.val();
var first = fnLn.isTerminated;
console.log("terminateStatus", first);
});
}//end of for loop
resolve();
}); //end of snap
});
}
so in function getDataFirebase, data is retrieved from firebase and is push to an array called , Collect. So, after pushing 1 row, it query again to the firebase for another table of data then continues the loop. The problem here is that, i wanted to finish all processes before it resolves the promise.
the output of console according to the code is as follow:
Collect (array)
first
Second
terminateStatus, 1
it must be
Collect (array)
first
terminateStatus,1
second

I'm not 100% sure how your code is working but it looks like you're doing the right thing on refReview.on("value"): creating a promise before calling Firebase, and then resolving it afterwards. But you're not doing that on getDataToUsers.once("value"). There, you're firing the event and not waiting for it to return — the for-loop continues on, and all the callbacks are processed later, but resolve is at the end of the for-loop, so it's too late by then.
My guess is that you thought the async keyword would cause the for-loop to wait for that job to complete, but actually all it does here is cause the callback to return a promise — which is ignored by the on function.
You have a few options, but probably the best will be to use Promise.all, which accepts an array of promises and waits for them all to be resolved — but you should probably use Firebase's Promise API rather than bolting promises onto the event API. So it'll look something like:
refReview.once('value')
.then(snap => Promise.all(snap.val().map(item =>
firebase.database()
.ref("users")
.child(item.revieweeID.userID)
.once('value')
.then(snap => {
console.log('terminated');
])
})));
(except with the actual functionality added in, natch)

Related

Struggling to get my promises to work in an array loop

I have been struggling with this issue for a week and have researched myself close to death. I am a total newbie. I have managed to grasp the crux of promises, but I am failing to see how to include this in a loop.
I have an app that is looking through an array. There is some validation of the array against a mongoose database (which is taking time to run). I am trying to push items into a new array based on some of this validation. I know the validation is working because of the console log in the loop. However my final array is not waiting for the loop to finish. Which means I need to put the loop into a promise, or well I think, but the issue is that I don't know how to resolve it. Current output is a blank array instead of the validated array. Here is my code:
//dummy data of an array - this is originally extracted from a mongoose DB and works (it's my first promise).
const appArray = ["5f8f25d554f1e43f3089ea5d",
"5f8f25e854f1e43f3089ea5e",
"5f8f25f454f1e43f3089ea5f",
"5f8f314ab92c7f406f28b83a",
"5f8fe50a9d44694cad91a01b",
"5f92e8a75d848870e015dff3",
"5f92e8b35d848870e015dff4",
"5f92e8cb5d848870e015dff5",
"5f8fe51d9d44694cad91a01c"];
//the second promise takes the array above and validates it against another collection on mongoose
function myPromise2 (response){
return new Promise((resolve, reject) => {
let appoints = [];
response.forEach(e => {
//loop through each item of the array and look against Appointment collection
Appointment.findById(e, function(err, foundApp){
//some validation supposed to happen here and then pushed into a new array
appoints.push(foundApp);
console.log(appoints);
})
})
//once completed supposed to resolve and return
resolve(appoints);
})
};
myPromise2(appArray).then((response) => {console.log(response)});
Here is an example which should work. Add a promise for each element to the array and then resolve the outer function if all promises have resolved.
// dummy data of an array - this is originally extracted from a mongoose DB and works (it's my first promise).
const appArray = ["5f8f25d554f1e43f3089ea5d",
"5f8f25e854f1e43f3089ea5e",
"5f8f25f454f1e43f3089ea5f",
"5f8f314ab92c7f406f28b83a",
"5f8fe50a9d44694cad91a01b",
"5f92e8a75d848870e015dff3",
"5f92e8b35d848870e015dff4",
"5f92e8cb5d848870e015dff5",
"5f8fe51d9d44694cad91a01c"];
// the second promise takes the array above and validates it against another collection on mongoose
function myPromise2 (response) {
let appoints = [];
response.forEach(e => {
appoints.push(new Promise((resolve) => {
//loop through each item of the array and look against Appointment collection
Appointment.findById(e, function(err, foundApp) {
//some validation supposed to happen here and then pushed into a new array
resolve(foundApp);
})
}))
})
return Promise.all(appoints)
};
myPromise2(appArray).then((response) => {console.log(response)});
Points to address:
Use the promise that mongoose provides through the .exec() method. This way you don't need new Promise
Collect these individual promises in an array (use .map instead of .forEach), and pass this array to Promise.all
If you do this, the code for myPromise2 reduces to the following:
function myPromise2 (response){
return Promise.all(response.map(e => Appointment.findById(e).exec()));
}
here is my suggestion:
const appArray = [
"5f8f25d554f1e43f3089ea5d",
"5f8f25e854f1e43f3089ea5e",
"5f8f25f454f1e43f3089ea5f",
"5f8f314ab92c7f406f28b83a",
"5f8fe50a9d44694cad91a01b",
"5f92e8a75d848870e015dff3",
"5f92e8b35d848870e015dff4",
"5f92e8cb5d848870e015dff5",
"5f8fe51d9d44694cad91a01c"
];
function myPromise2 (response){
return Promise.all(response.map(id) => {
return Appointment.findById(id).exec();
})
};
myPromise2(appArray)
.then(console.log) // response array
.catch(// handle errors)
// you can also async/await the calling part
you can also use one of:
Promise.allSettled
Prmoise.any (es2021)
Promise.race
it's just depends on how you would like to handle the responses/failures.
A good alternative to consider maybe Async/Await and have a look Async_await. This will hopefully answer all your issues
It's probably a good idea to look into how the JS event loop system works guide here,

How to know when a fetch ends without locking the body stream?

I'm making requests to an API, but their server only allows a certain number of active connections, so I would like to limit the number of ongoing fetches. For my purposes, a fetch is only completed (not ongoing) when the HTTP response body arrives at the client.
I would like to create an abstraction like this:
const fetchLimiter = new FetchLimiter(maxConnections);
fetchLimiter.fetch(url, options); // Returns the same thing as fetch()
This would make things a lot simpler, but there seems to be no way of knowing when a stream being used by other code ends, because streams are locked while they are being read. It is possible to use ReadableStream.tee() to split the stream into two, use one and return the other to the caller (possibly also constructing a Response with it), but this would degrade performance, right?
Since fetch uses promises, you can take advantage of that to make a simple queue system.
This is a method I've used before for queuing promise based stuff. It enqueues items by creating a Promise and then adding its resolver to an array. Of course until that Promise resolves, the await keeps any later promises from being invoked.
And all we have to do to start the next fetch when one finishes is just grab the next resolver and invoke it. The promise resolves, and then the fetch starts!
Best part, since we don't actually consume the fetch result, there's no worries about having to clone or anything...we just pass it on intact, so that you can consume it in a later then or something.
*Edit: since the body is still streaming after the fetch promise resolves, I added a third option so that you can pass in the body type, and have FetchLimiter retrieve and parse the body for you.
These all return a promise that is eventually resolved with the actual content.
That way you can just have FetchLimiter parse the body for you. I made it so it would return an array of [response, data], that way you can still check things like the response code, headers, etc.
For that matter, you could even pass in a callback or something to use in that await if you needed to do something more complex, like different methods of parsing the body depending on response code.
Example
I added comments to indicate where the FetchLimiter code begins and ends...the rest is just demo code.
It's using a fake fetch using a setTimeout, which will resolve between 0.5-1.5 secs. It will start the first three requests immediately, and then the actives will be full, and it will wait for one to resolve.
When that happens, you'll see the comment that the promise has resolved, then the next promise in the queue will start, and then you'll see the then from in the for loop resolve. I added that then just so you could see the order of events.
(function() {
const fetch = (resource, init) => new Promise((resolve, reject) => {
console.log('starting ' + resource);
setTimeout(() => {
console.log(' - resolving ' + resource);
resolve(resource);
}, 500 + 1000 * Math.random());
});
function FetchLimiter() {
this.queue = [];
this.active = 0;
this.maxActive = 3;
this.fetchFn = fetch;
}
FetchLimiter.prototype.fetch = async function(resource, init, respType) {
// if at max active, enqueue the next request by adding a promise
// ahead of it, and putting the resolver in the "queue" array.
if (this.active >= this.maxActive) {
await new Promise(resolve => {
this.queue.push(resolve); // push, adds to end of array
});
}
this.active++; // increment active once we're about to start the fetch
const resp = await this.fetchFn(resource, init);
let data;
if (['arrayBuffer', 'blob', 'json', 'text', 'formData'].indexOf(respType) >= 0)
data = await resp[respType]();
this.active--; // decrement active once fetch is done
this.checkQueue(); // time to start the next fetch from queue
return [resp, data]; // return value from fetch
};
FetchLimiter.prototype.checkQueue = function() {
if (this.active < this.maxActive && this.queue.length) {
// shfit, pulls from start of array. This gives first in, first out.
const next = this.queue.shift();
next('resolved'); // resolve promise, value doesn't matter
}
}
const limiter = new FetchLimiter();
for (let i = 0; i < 9; i++) {
limiter.fetch('/mypage/' + i)
.then(x => console.log(' - .then ' + x));
}
})();
Caveats:
I'm not 100% sure if the body is still streaming when the promise resolves...that seems to be a concern for you. However if that's a problem you could use one of the Body mixin methods like blob or text or json, which doesn't resolve until the body content is completely parsed (see here)
I intentionally kept it very short (like 15 lines of actual code) as a very simple proof of concept. You'd want to add some error handling in production code, so that if the fetch rejects because of a connection error or something that you still decrement the active counter and start the next fetch.
Of course it's also using async/await syntax, because it's so much easier to read. If you need to support older browsers, you'd want to rewrite with Promises or transpile with babel or equivalent.

Async function not returning value, but console.log() does: how to do? [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 5 years ago.
I have an es6 class, with an init() method responsible for fetching data, transforming it, then update the class's property this.data with newly transformed data.
So far so good.
The class itself has another getPostById() method, to just do what it sounds like. Here is the code for the class:
class Posts {
constructor(url) {
this.ready = false
this.data = {}
this.url = url
}
async init() {
try {
let res = await fetch( this.url )
if (res.ok) {
let data = await res.json()
// Do bunch of transformation stuff here
this.data = data
this.ready = true
return data
}
}
catch (e) {
console.log(e)
}
}
getPostById(id){
return this.data.find( p => p.id === id )
}
}
Straightforward, except I have an async/await mechanism in the init() method.
Now, this code will work correctly:
let allPosts = new Posts('https://jsonplaceholder.typicode.com/posts')
allPosts.init()
.then( d => console.log(allPosts.getPostById(4)) )
// resulting Object correctly logged in console
but it only gets printed into the console:
How could I use allPosts.getPostById(4) as a return of a function ?
Like:
let myFunc = async () => {
const postId = 4
await allPosts.init() // I need to wait for this to finish before returning
// This is logging correct value
console.log( 'logging: ' + JSON.stringify(allPosts.getPostById( postId ), null, 4) )
// How can I return the RESULT of allPosts.getPostById( postId ) ???
return allPosts.getPostById( postId )
}
myFunc() returns a Promise but not the final value. I have read several related posts on the subject but they all give example of logging, never returning.
Here is a fiddle that includes two ways of handling init(): using Promise and using async/await. No matter what I try, I can't manage to USE the FINAL VALUE of getPostById(id).
The question of this post is: how can I create a function that will RETURN the VALUE of getPostById(id) ?
EDIT:
A lot of good answers trying to explain what Promises are in regards to the main execution loop.
After a lot of videos and other good reads, here is what I understand now:
my function init() correctly returns. However, within the main event loop: it returns a Promise, then it is my job to catch the result of this Promise from within a kinda parallel loop (not a new real thread). In order to catch the result from the parallel loop there are two ways:
use .then( value => doSomethingWithMy(value) )
use let value = await myAsyncFn(). Now here is the foolish hiccup:
await can only be used within an async function :p
thus itself returning a Promise, usable with await which should be embed in an async function, which will be usable with await etc...
This means we cannot really WAIT for a Promise: instead we should catch parallel loop indefinitely: using .then() or async/await.
Thanks for the help !
As for your comment; I'll add it as answer.
The code you write in JavaScript is run on one thread, that means that if your code could actually wait for something it will block any of your other code from getting executed. The event loop of JavaScript is explained very well in this video and if you like to read in this page.
A good example of blocking code in the browser is alert("cannot do anything until you click ok");. Alert blocks everything, the user can't even scroll or click on anything in the page and your code also blocks from executing.
Promise.resolve(22)
.then(x=>alert("blocking")||"Hello World")
.then(
x=>console.log(
"does not resolve untill you click ok on the alert:",
x
)
);
Run that in a console and you see what I mean by blocking.
This creates a problem when you want to do something that takes time. In other frameworks you'd use a thread or processes but there is no such thing in JavaScript (technically there is with web worker and fork in node but that's another story and usually far more complicated than using async api's).
So when you want to make a http request you can use fetch but fetch takes some time to finish and your function should not block (has to return something as fast as possible). This is why fetch returns a promise.
Note that fetch is implemented by browser/node and does run in another thread, only code you write runs in one thread so starting a lot of promises that only run code you write will not speed up anything but calling native async api's in parallel will.
Before promises async code used callbacks or would return an observable object (like XmlHttpRequest) but let's cover promises since you can convert the more traditional code to a promise anyway.
A promise is an object that has a then function (and a bunch of stuff that is sugar for then but does the same), this function takes 2 parameters.
Resolve handler: A function that will be called by the promise when the promise resolves (has no errors and is finished). The function will be passed one argument with the resolve value (for http requests this usually is the response).
Reject handler: A function that will be called by the promise when the promise rejects (has an error). This function will be passed one argument, this is usually the error or reason for rejection (can be a string, number or anything).
Converting callback to promise.
The traditional api's (especially nodejs api's) use callbacks:
traditionalApi(
arg
,function callback(err,value){
err ? handleFail(err) : processValue(value);
}
);
This makes it difficult for the programmer to catch errors or handle the return value in a linear way (from top to bottom). It gets even more impossible to try and do things parallel or throttled parallel with error handling (impossible to read).
You can convert traditional api's to promises with new Promise
const apiAsPromise = arg =>
new Promise(
(resolve,reject)=>
traditionalApi(
arg,
(err,val) => (err) ? reject(err) : resolve(val)
)
)
async await
This is what's called syntax sugar for promises. It makes promise consuming functions look more traditional and easier to read. That is if you like to write traditional code, I would argue that composing small functions is much easier to read. For example, can you guess what this does?:
const handleSearch = search =>
compose([
showLoading,
makeSearchRequest,
processRespose,
hideLoading
])(search)
.then(
undefined,//don't care about the resolve
compose([
showError,
hideLoading
])
);
Anayway; enough ranting. The important part is to understand that async await doesn't actually start another thread, async functions always return a promise and await doesn't actually block or wait. It's syntax sugar for someFn().then(result=>...,error=>...) and looks like:
async someMethod = () =>
//syntax sugar for:
//return someFn().then(result=>...,error=>...)
try{
const result = await someFn();
...
}catch(error){
...
}
}
The examples allways show try catch but you don't need to do that, for example:
var alwaysReject = async () => { throw "Always returns rejected promise"; };
alwaysReject()
.then(
x=>console.log("never happens, doesn't resolve")
,err=>console.warn("got rejected:",err)
);
Any error thrown or await returning a rejected promise will cause the async function to return a rejected promise (unless you try and catch it). Many times it is desirable to just let it fail and have the caller handle errors.
Catching errors could be needed when you want the promise to succeed with a special value for rejected promises so you can handle it later but the promise does not technically reject so will always resolve.
An example is Promise.all, this takes an array of promises and returns a new promise that resolves to an array of resolved values or reject when any one of them rejects. You may just want to get the results of all promises back and filter out the rejected ones:
const Fail = function(details){this.details=details;},
isFail = item => (item && item.constructor)===Fail;
Promise.all(
urls.map(//map array of urls to array of promises that don't reject
url =>
fetch(url)
.then(
undefined,//do not handle resolve yet
//when you handle the reject this ".then" will return
// a promise that RESOLVES to the value returned below (new Fail([url,err]))
err=>new Fail([url,err])
)
)
)
.then(
responses => {
console.log("failed requests:");
console.log(
responses.filter(//only Fail type
isFail
)
);
console.log("resolved requests:");
console.log(
responses.filter(//anything not Fail type
response=>!isFail(response)
)
);
}
);
Your question and the comments suggest you could use a little intuition nudge about the way the event loop works. It really is confusing at first, but after a while it becomes second nature.
Rather than thinking about the FINAL VALUE, think about the fact that you have a single thread and you can't stop it — so you want the FUTURE VALUE -- the value on the next or some future event loop. Everything you write that is not asynchronous is going to happen almost immediately — functions return with some value or undefined immediately. There's nothing you can do about. When you need something asynchronously, you need to setup a system that is ready to deal with the async values when they return sometime in the future. This is what events, callbacks, promises (and async/await) all try to help with. If some data is asynchronous, you simply can not use it in the same event loop.
So what do you do?
If you want a pattern where you create an instance, call init() and then some function that further process it, you simply need to setup a system that does the processing when the data arrives. There are a lot of ways to do this. Here's one way that's a variation on your class:
function someAsync() {
console.log("someAsync called")
return new Promise(resolve => {
setTimeout(() => resolve(Math.random()), 1000)
})
}
class Posts {
constructor(url) {
this.ready = false
this.data = "uninitilized"
this.url = url
}
init() {
this.data = someAsync()
}
time100() {
// it's important to return the promise here
return this.data.then(d => d * 100)
}
}
let p = new Posts()
p.init()
processData(p)
// called twice to illustrate point
processData(p)
async function processData(posts) {
let p = await posts.time100()
console.log("randomin * 100:", p)
}
init() saves the promise returned from someAsync(). someAsync() could be anything that returns a promise. It saves the promise in an instance property. Now you can call then() or use async/await to get the value. It will either immediately return the value if the promise has already resolved or it will deal with it when it has resolved. I called processData(p) twice just to illustrate that it doesn't calle the someAsync() twice.
That's just one pattern. There are a lot more — using events, observables, just using then() directly, or even callbacks which are unfashionable, but still can be useful.
NOTE: Wherever you use await it has to be inside an async function.
Check out the UPDATED FIDDLE
You need to use await myFunc() to get the value you expect from getPostById because an async function always returns a promise.
This sometimes is very frustrating as the whole chain needs to be converted into async functions but that's the price you pay for converting it to a synchronous code, I guess. I am not sure if that can be avoided but am interested in hearing from people who have more experience on this.
Try out the below code in your console by copying over the functions and then accessing final and await final.
NOTE:
An async function CAN contain an await expression.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
There is no rule that is must have await in order to even declare an async function.
The example below uses an async function without await just to show that an async function always returns a promise.
const sample = async () => {
return 100;
}
// sample() WILL RETURN A PROMISE AND NOT 100
// await sample() WILL RETURN 100
const init = async (num) => {
return new Promise((resolve, reject) => {
resolve(num);
});
}
const myFunc = async (num) => {
const k = await init(num);
return k;
}
// const final = myFunc();
// final; This returns a promise
// await final; This returns the number you provided to myFunc

Async findOne() operation inside forEach Loop

I am having a hard time understanding JavaScript Promises. I am searching on of my Mongoose models for objects that meet a certain condition and if they exist, I want to make the object into a plain JS object and add a property onto it.
Unfortunately, I am unable to wrap my head around how I can ensure my forEach loop will run completely before my promise ends up resolving. Please see my code.
// Called to check whether a user has participated in a given list of challenges
participationSchema.statics.getParticipation = function(user, challenges) {
return new Promise((resolve, reject) => {
challengesArray = [];
challenges.forEach((challenge) => {
// Model#findOne() is Async--how to ensure all these complete before promise is resolved?
Participation.findOne({user, challenge})
.then((res) => {
if (res) {
var leanObj = challenge.toObject();
leanObj.participation = true;
challengesArray.push(leanObj);
}
})
.catch(e => reject(e));
})
console.log("CHALLENGES ARRAY", challengesArray); // Challenges Array empty :(
resolve(challengesArray);
});
}
I've looked through similar questions, but am unable to get to an answer. Appreciate the help.
So, what is happening when you call getParticipation is that the forEach loop runs all the way and all individual promises for Participation.findOne are created but not yet resolved. The execution doesn't wait for them to resolve and continues after the forEach, resolving the top-level promise challengesArray, which is still empty at that point. Sometime after, the promises created in the forEach start resolving but their results are now lost.
Also, as Bergi mentioned in the comments, nesting promises is considered an anti-pattern; promises should be chained, not nested.
What you want is to use something like Promise.all to wait for all of your promises to finish first, then you filter out all non-existing results and finally return the array.
participationSchema.statics.getParticipation = function(user, challenges) {
return Promise.all(challenges.map(challenge => {
return Participation.findOne({user, challenge}).then(result => {
if (result) {
var leanObj = challenge.toObject();
leanObj.participation = true;
return leanObj;
}
});
})
// at this point, results contains an array of `leanObject` and `undefined` depending if the `findOne` call returned anything and the code withing the `if` above was run
.then((results) => {
return results.filter(result => !!result) // filter out `undefined` results so we only end up with lean objects
});
}

Correct way to chain multiple async functions on array items with callback

My title is kind of gross. Let me elaborate:
I'm using node 7.2.0 and Electron. I have an array of Song objects with a key called filepath. I need to do the following for each song, in order:
Get their ID3 metadata
Send artist and track info to API
Apply correct track information from API to each object
Sort into albums
Display results
So what I need to know is this: what is the best way to chain aync functions, and how can I implement a callback when the chain completes.
Should I run each song through all the steps as soon as I can? Should I wait until all songs are done step 1, then move to step 2, then step 3 when all are finished step 2, etc.
Secondly, what's the accepted way to run a callback when all the above steps (probably in a forEach loop) finish? Should I use the node async library or promises? I've been reading that I should wrap each singular function in a promise then use Promise.all() to run a callback, but I'm not sure if that's up to date.
Edit: Code example. It's not much, but it's all I have. I decided to ask this before I dive too far in and regret my choice.
let songs = SongHelpers.createSongArray(MUSIC_DIR);
songs.forEach((song) => {
// I'm putting the contents of the applyMetadata function here to ease readability for this question
// SongHelpers.applyMetadata(song, callback);
// mm is from the musicmetadata package: https://github.com/leetreveil/musicmetadata
mm(fs.createReadStream(song.filepath), function (err, metadata) {
try {
if (err) throw (err);
return metadata;
} catch (e) {
console.error(`Error in mm parsing: ${e.message}`);
}
});
// Then send to API
// Then apply API response to each track
// etc.
});
An overview of what you can do with Promises
let songs = SongHelpers.createSongArray(MUSIC_DIR);
Promise.all(songs.map(song => new Promise((resolve, reject) => {
mm(fs.createReadStream(song.filepath), function (err, metadata) {
if (err) {
return reject(err);
}
resolve(metadata);
})).then(metadata => {
// Send artist and track info to API,
// should return a Promise that resolves when the API returns a result
}).then(result => {
// apply correct track information, return some value that could be used in the sort step
});
})).then(results => {
// results is an array of values returned in the last step above
// sort and display here
}).catch(err => {
// handle error here
});

Categories

Resources