I am trying to use a for loop to iterate through an array and perform an async function on each item. I want this to happen in a sequential order and I don't want the next async function for the next item in the array to execute until the async function for the previous item has COMPLETED.
I am using await and promises to try to do this. However, I must be doing something wrong because the code does not seem to be working right. The for loop only seems to work once.
Here is my code:
function asyncFunc(i)
{
//Here I am updating my database which is the async part of this function
return db.collection("collection").doc("doc").update({
})
.then(function(){
let d = new Date();
console.log(i + " async COMPLETED at: " + d.getTime());
return new Promise((resolve, reject) => {
resolve;
});
});
}
async function doWork()
{
var numbers = [1,2,3,4];
for(const i of numbers)
{
var d = new Date();
console.log(i + " async CALLED at: " + d.getTime());
await asyncFunc(i);
}
}
doWork();
Here is the console output:
1 async CALLED at: 1594683980009
1 async COMPLETED at: 1594683980280
Why is this happening?
So, you are returning two promises, which is a problem because you resolve one only to create a second which the main thread is then waiting on. All you have to do is return the promise that db.collection... gives you.
Fundamentally, the problem is that the new promise you create isn't resolved properly. You just say that the promise is (resolve, reject) => {resolve;} which is A) not even really a function and B) not going to resolve. Because you didn't call the resolve function. Truly though, this promise should never have been created because it was completely unnecessary, so I would worry more about just handling promises with simple awaits and returns than bothering with creating them. TL;DR Don't create a promise if there is already a promise that exists which you can use.
This code should work:
function asyncFunc(i)
{
//Here I am updating my database which is the async part of this function
return db.collection("collection").doc("doc").update({
})
.then(() =>{
let d = new Date();
console.log(i + " async COMPLETED at: " + d.getTime());
});
}
async function doWork()
{
var numbers = [1,2,3,4];
for(const i of numbers)
{
var d = new Date();
console.log(i + " async CALLED at: " + d.getTime());
await asyncFunc(i);
}
}
doWork();
For the sake of clarity regarding the actual issue you ran into, the problem is with this block of your code:
return new Promise((resolve, reject) => {
resolve;
});
The problem is that resolve needs to be a function that actually executes. Thus what you needed was this:
return new Promise((resolve, reject) => {
resolve();
});
Related
Below code only prints "delay of 2000 executed". I get it that a promise only executes once. But this is not the same promise that I create in next line
function later(delayMillis) {
return new Promise(function(resolve) {
setTimeout(()=>{console.log('delay of ' + delayMillis + ' executed');}, delayMillis);
});
}
(async () => {
await later(2000);
await later(3000);
console.log('This should print after minimum 5 seconds');
})();
If I change the function to return execute resolve instead of the console log I specified, I do get the output "This should print after minimum 5 second". Why is it that both promises execute in this case?
function later(delayMillis) {
return new Promise(function(resolve) {
setTimeout(resolve, delayMillis);
});
}
(async () => {
await later(2000);
await later(3000);
console.log('This should print after minimum 5 seconds');
})();
You need to edit the later function to be as follows:
function later(delayMillis) {
return new Promise(function(resolve) {
setTimeout(()=>{console.log('delay of ' + delayMillis + ' executed'); resolve();}, delayMillis);
});
}
The reason is because await does not move to the next instruction unless it is resolved or rejected.
You tried using either console.log or resolve. Why not use both!!
This is a question up there. The promises are all built in such a form that they listen for the resolve. It is almost like handling an asynchronous event, which resolve stands for. console.log is another kind here.
new Promise((res)=>{res('This should print')}).then(console.log);
is a general case.
Your case is similar.
You and me shouldn't know just why all happens, but how to use it!
In the following code:
new Promise((resolve, reject) => {
asyncFunc1(resolve,reject);
})
.then((result) => {
// do then1 stuff
})
new Promise((resolve, reject) => {
asyncFunc2(resolve,reject);
})
.then((result) => {
// do then2 stuff
})
Will asyncFunc1()'s .then() complete before asyncFunc2() is executed?
Or will both execute (nearly) simultaneously, and their then()s execute just whenever they happen to return?
If the second one does not wait for the first one, is there a way to make it do so other than moving asyncFunc2() into asyncFunc1()'s .then()?
Both promises will execute (nearly) simultaneously, because that is exactly one of the strengths of Javascript: Due to its callback-based design, it can kick off and wait for many asynchronous functions (in the form of promises) in parallel, without the need for the developer to care about complex handling of threads or anything like that.
If you want to have asyncFunc2 wait for the completion of asyncFunc1, it needs to be placed inside the then-block of asyncFunc1.
You can easily see and proof that by simulating two functions which take a defined time (using setTimeout), for example:
function resolveAfter2Seconds() {
return new Promise(resolve => {
setTimeout(() => {
resolve('resolved at ' + new Date().toISOString());
}, 2000);
});
}
function resolveAfter3Seconds() {
return new Promise(resolve => {
setTimeout(() => {
resolve('resolved at ' + new Date().toISOString());
}, 3000);
});
}
function execPromises() {
console.log('calling at ' + new Date().toISOString());
resolveAfter2Seconds().then(result => console.log(result));
resolveAfter3Seconds().then(result => console.log(result));
}
execPromises();
You will see in the console output that the first one will finish 2 sec after starting the script, the other one 3 sec after starting the script (and not after 2 + 3 = 5 sec).
If you want to make asyncFunc2 wait for asyncFunc1 without the need for then, you can use the async/await keywords.
In my example, the function execPromises would then look like this (asyncFunc2 executed after completion of asyncFunc1!):
async function execPromises() {
console.log('calling at ' + new Date().toISOString());
const resultA = await resolveAfter2Seconds();
console.log(resultA);
const resultB = await resolveAfter3Seconds();
console.log(resultB);
}
I'm working with fetching information from a github repository. I want to get the list of pull requests within that repo, get the list of commits associated with each pull request, then for each commit I want to get information such as the author of the commit, the number of files associated with each commit and the number of additions and deletions made to each file. I'm using axios and the github API to accomplish this. I know how to work with the API, but the promises and async functions are keeping me from accomplishing my task. I have the following code:
const axios = require('axios');
var mapOfInformationObjects = new Map();
var listOfCommits = [];
var listOfSHAs = [];
var gitApiPrefix = link I'll use to start fetching data;
var listOfPullRequestDataObjects = [];
var listOfPullRequestNumbers = [];
var mapOfPullNumberToCommits = new Map();
function getAllPullRequests(gitPullRequestApiLink) {
return new Promise((resolve, reject) => {
axios.get(gitPullRequestApiLink).then((response) =>{
listOfPullRequestDataObjects = response['data'];
var k;
for (k = 0; k < listOfPullRequestDataObjects.length; k++){
listOfPullRequestNumbers.push(listOfPullRequestDataObjects[k]['number']);
}
resolve(listOfPullRequestNumbers);
}).catch((error) => {
reject(error);
})
})
}
function getCommitsForEachPullRequestNumber(listOfPRNumbers) {
var j;
for (j = 0; j < listOfPRNumbers.length; j++) {
currPromise = new Promise((resolve, reject) => {
currentGitApiLink = gitApiPrefix + listOfPRNumbers[j] + "/commits";
axios.get(currentGitApiLink).then((response) => {
mapOfPullNumberToCommits.set(listOfPRNumbers[j], response['data']);
resolve("Done with Pull Request Number: " + listOfPRNumbers[j]);
}).catch((error) => {
reject(error);
})
})
}
}
function getListOfCommits(gitCommitApiLink){
return new Promise((resolve, reject) => {
axios.get(gitCommitApiLink).then((response) => {
resolve(response);
}).catch((error) => {
reject(error);
})
})
}
So far, I made some functions that I would like to call sequentially.
First I'd like to call getAllPullRequestNumbers(someLink)
Then I'd like to call getCommitsForEachPullRequestNumber(listofprnumbers)
Then getListOfCommits(anotherLink)
So it would look something like
getAllPullRequestNumbers(someLink)
getCommitsForEachPullRequestNumber(listofprnumbers)
getListOfCommits(anotherlink)
But two problems arise:
1) I'm not sure if this is how you would call the functions so that the first function in the sequence completes before the other.
2) Because I'm not familiar with Javascript, I'm not sure, especially with the getCommitsForEachPullRequestNumber function since you run a loop and call axios.get() on each iteration of the loop, if this is how you work with promises within the functions.
Would this be how you would go about accomplishing these two tasks? Any help is much appreciated. Thanks!
When you a number of asynchronous operations (represented by promises) that you can run all together and you want to know when they are all done, you use Promise.all(). You collect an array of promises and pass it to Promise.all() and it will tell you when they have all completed or when one of them triggers an error. If all completed, Promise.all() will return a promise that resolves to an array of results (one for each asynchronous operation).
When you're iterating an array to do your set of asynchronous operations, it then works best to use .map() because that helps you create a parallel array of promises that you can feed to Promise.all(). Here's how you do that in getCommitsForEachPullRequestNumber():
function getCommitsForEachPullRequestNumber(listOfPRNumbers) {
let mapOfPullNumberToCommits = new Map();
return Promise.all(listOfPRNumbers.map(item => {
let currentGitApiLink = gitApiPrefix + item + "/commits";
return axios.get(currentGitApiLink).then(response => {
// put data into the map
mapOfPullNumberToCommits.set(item, response.data);
});
})).then(() => {
// make resolved value be the map we created, now that everything is done
return mapOfPullNumberToCommits;
});
}
// usage:
getCommitsForEachPullRequestNumber(list).then(results => {
console.log(results);
}).catch(err => {
console.log(err);
});
Then, in getListOfCommits(), since axios already returns a promise, there is no reason to wrap it in a manually created promise. That is, in fact, consider a promise anti-pattern. Instead, just return the promise that axios already returns. In fact, there's probably not even a reason to have this as a function since one can just use axios.get() directly to achieve the same result:
function getListOfCommits(gitCommitApiLink){
return axios.get(gitCommitApiLink);
}
Then, in getAllPullRequests() it appears you are just doing one axios.get() call and then processing the results. That can be done like this:
function getAllPullRequests(gitPullRequestApiLink) {
return axios.get(gitPullRequestApiLink).then(response => {
let listOfPullRequestDataObjects = response.data;
return listOfPullRequestDataObjects.map(item => {
return item.number;
});
});
}
Now, if you're trying to execute these three operations sequentially in this order:
getAllPullRequests(someLink)
getCommitsForEachPullRequestNumber(listofprnumbers)
getListOfCommits(anotherlink)
You can chain the promises from those three operations together to sequence them:
getAllPullRequests(someLink)
.then(getCommitsForEachPullRequestNumber)
.then(mapOfPullNumberToCommits => {
// not entirely sure what you want to do here, perhaps
// call getListOfCommits on each item in the map?
}).catch(err => {
console.log(err);
});
Or, if you put this code in an async function, then you can use async/awit:
async function getAllCommits(someLink) {
let pullRequests = await getAllPullRequests(someLink);
let mapOfPullNumberToCommits = await getCommitsForEachPullRequestNumber(pullRequests);
// then use getlistOfCommits() somehow to process mapOfPullNumberToCommits
return finalResults;
}
getAllCommits.then(finalResults => {
console.log(finalResults);
}).catch(err => {
console.log(err);
});
not as clean as jfriend00 solution,
but I played with your code and it finally worked
https://repl.it/#gui3/githubApiPromises
you get the list of commits in the variable listOfCommits
I don't understand the purpose of your last function, so I dropped it
I'm trying to learn about what the promise is and how to convert callback to promise. While I'm converting my code to promise I got very confused about the ref. I would very appreciate it if you show me how to convert this code as a simple example.
database.ref('/users').on("child_added").then(function(snap){
var subData = snap.val();
database.ref('/subs/' + subData.subid + '/pri/' + snap.key).once("value").then(function(userSnap) {
var userData = userSnap.val();
subData.name = userData.name;
subData.age = userData.age;
database.ref('/subs/' + subData.subid).once("value",function(subDSnap) {
var subDData = subDSnap.val();
subData.type = subDData.type;
database_m.ref('/users/' + snap.key).set(subData);
});
});
});
A Promise is not a replacement for every type of callback; rather it's an abstraction around one particular task that will either succeed once or fail once. The code you're converting looks more like an EventEmitter, where an event can occur multiple times, so replacing .on('child_added', ...) with a Promise implementation is not a good fit.
However, later on, you have a .once(...) call. That's a bit closer to a Promise in that it will only complete once. So if you really wanted to convert that, here's what it could look like:
function get(database, url) {
return new Promise(function (resolve, reject) {
database
.ref(url)
.once('value', resolve)
.once('error', reject);
});
}
database.ref('/users').on("child_added", function(snap) {
var subData = snap.val();
get(database, '/subs/' + subData.subid + '/pri/' + snap.key)
.then(function(userSnap) {
var userData = userSnap.val();
subData.name = userData.name;
subData.age = userData.age;
return get(database, '/subs/' + subData.subid);
})
.then(function(subDSnap) {
var subDData = subDSnap.val();
subData.type = subDData.type;
database_m.ref('/users/' + snap.key).set(subData);
})
.catch(function (err) {
// handle errors
});
});
});
I am not sure I understand the question and if your "on" is returning a promise or just listening, but in your code you have nested '.then', which is not the common way to deal with promises and I am not sure that's what you wanted to achieve here.
You could do (assuming that the on function returns a promise, which I doubt)
database.ref('/users').on("child_added")
.then(function(snap){/* do something with the first part of your snap function*/})
.then (results => {/* do something else, such as your second ref call*/})
.catch(error => {/* manage the error*/})
To learn about promises, there are many examples online but what I really liked is the promise tutorial at google, with this nice snippet that explains it
var promise = new Promise(function(resolve, reject) {
// do a thing, possibly async, then…
if (/* everything turned out fine */) {
resolve("Stuff worked!");
}
else {
reject(Error("It broke"));
}
});
then, once you have the function that returns this promise, you can start doing
.then(...)
.then(...)
.then(...)
.catch(...)
I'm new to RxJS and FRP in general. I had the idea of converting an existing promise chain in my ExpressJS application to be an observable for practice. I am aware that this probably isn't the best example but maybe someone can help shed some light.
What I'm trying to do:
I have two promises - prom1 and prom2
I want prom1 to run before prom2
If prom1 sends a reject(err), I want to cancel prom2 before it starts.
I want the error message prom1 returns to be available to the onError method on the observer.
var prom1 = new Promise(function(resolve, reject) {
if (true) {
reject('reason');
}
resolve(true);
});
var prom2 = new Promise(function(resolve, reject) {
resolve(true);
});
// What do I do here? This is what I've tried so far...
var source1 = Rx.Observable.fromPromise(prom1);
var source2 = source1.flatMap(Rx.Observable.fromPromise(prom2));
var subscription = source2.subscribe(
function (result) { console.log('Next: ' + result); },
// I want my error 'reason' to be made available here
function (err) { console.log('Error: ' + err); },
function () { console.log('Completed'); });
If I understood what you are trying to do - you need to create two deferred observables from functions that return promises and concat them:
var shouldFail = false;
function action1() {
return new Promise(function (resolve, reject) {
console.log('start action1');
if (shouldFail) {
reject('reason');
}
resolve(true);
});
}
function action2() {
return new Promise(function (resolve, reject) {
console.log('start action2');
resolve(true);
});
}
var source1 = Rx.Observable.defer(action1);
var source2 = Rx.Observable.defer(action2);
var combination = Rx.Observable.concat(source1, source2);
var logObserver = Rx.Observer.create(
function (result) {
console.log('Next: ' + result);
},
function (err) {
console.log('Error: ' + err);
},
function () {
console.log('Completed');
});
then for normal case:
combination.subscribe(logObserver);
// start action1
// Next: true
// start action2
// Next: true
// Completed
And case where fisrt promise fails:
shouldFail = true;
combination.subscribe(logObserver);
// start action1
// Error: reason
http://jsfiddle.net/cL37tgva/
flatMap turns an Observable of Observables into an Observable. It's used in many examples with Promises because often you have an observable and in the map function you want to create a promise for each "item" the observable emmits. Because every fromPromise call creates a new Observable, that makes it an "observable of observables". flatMap reduces that to a "flat" observable.
In your example you do something different, you turn a single promise into an observable and want to chain it with another observable (also created form a single promise). Concat does what you are looking for, it chains two observables together.
The error case will work as you would expect.
Observable.forkJoin works great here receiving array of other Observables.
Rx.Observable.forkJoin([this.http.get('http://jsonplaceholder.typicode.com/posts'), this.http.get('http://jsonplaceholder.typicode.com/albums')]).subscribe((data) => {
console.log(data);
});