Array undefined after resolving it after for loop - javascript

I have the problem of having an undefined array which gets resolved after a for loop where it gets filled. It looks like the following:
function mainFunction() {
getUnreadMails().then(function(mailArray) {
// Do stuff with the mailArray
// Here it is undefined
})
}
function getUnreadMails() {
var mailArray = [];
return new Promise(function(resolve, reject) {
listMessages(oauth2Client).then(
(messageIDs) => {
for(var i = 0; i < messageIDs.length; i++) {
getMessage(oauth2Client, 'me', messageIDs[i]).then(function(r) {
// Array gets filled
mailArray.push(r);
}, function(error) {
reject(error);
})
}
// Array gets resolved
resolve(mailArray);
},
(error) => {
reject(error);
}
)
});
}
Both listMessages() and getMessage() returns a promise, so it is chained here. Any ideas why I am getting an undefined mailArray? My guess is that it is not filled yet when it gets resolved. Secondly I think this flow is not a good practice.

The Array is probably undefined because it is never defined; at least nowhere in your code. And your promise resolves before any iteration in your loop can resolve or better said, throw (trying to push to undefined).
Besides that. you can highly simplyfy your code by using Array#map and Promise.all.
And there's no point in catching an Error just to rethrow the very same error without doing anything else/with that error.
function getUnreadMails() {
//put this on a seperate line for readability
//converts a single messageId into a Promise of the result
const id2message = id => getMessage(oauth2Client, 'me', id);
return listMessages(oauth2Client)
//converts the resolved messageId into an Array of Promises
.then(messageIDs => messageIDs.map( id2message ))
//converts the Array of Promises into an Promise of an Array
//.then(Promise.all.bind(Promise));
.then(promises => Promise.all(promises));
//returns a Promise that resolves to that Array of values
}
or short:
function getUnreadMails() {
return listMessages(oauth2Client)
.then(messageIDs => Promise.all(messageIDs.map( id => getMessage(oauth2Client, 'me', id) )))
}
.then(Promise.all) won't work
I wanted to make the intermediate results more clear by seperating them into distinct steps/functions. But I typed too fast and didn't check it. Fixed the code.
In the short version, where does the mailArray then actually get filled/resolved
Promise.all() takes an an Array of promises and returns a single promise of the resolved values (or of the first rejection).
messageIDs.map(...) returns an Array and the surrounding Promise.all() "converts" that into a single Promise of the resolved values.
And since we return this Promise inside the Promise chain, the returned promise (listMessages(oauth2Client).then(...)) also resolves to this Array of values.

Just picking up on marvel308's answer, I think you need to create a new Promise that resolves when your other ones do. I haven't had a chance to test this, but I think this should work
function getUnreadMails() {
var mailArray = [];
return new Promise(function(resolve, reject) {
listMessages(oauth2Client).then(
(messageIDs) => {
var messages = [];
for(var i = 0; i < messageIDs.length; i++) {
messages.push(
getMessage(oauth2Client, 'me', messageIDs[i]).catch(reject)
);
}
Promise.all(messages).then(resolve);
},
(error) => {
reject(error);
}
)
});
}
This way, the resolve of your first promise gets called when all the messages have resolved

getMessage(oauth2Client, 'me', messageIDs[i]).then(function(r) {
// Array gets filled
mailArray.push(r);
}, function(error) {
reject(error);
})
is an asynchronous call
resolve(mailArray);
won't wait for it to push data and would resolve the array before hand
to resole this you should use Promise.all()
function mainFunction() {
getUnreadMails().then(function(mailArray) {
// Do stuff with the mailArray
// Here it is undefined
})
}
function getUnreadMails() {
var mailArray = [];
return listMessages(oauth2Client).then(
(messageIDs) => {
for(var i = 0; i < messageIDs.length; i++) {
mailArray.push(getMessage(oauth2Client, 'me', messageIDs[i]));
}
// Array gets resolved
return Promise.all(mailArray);
},
(error) => {
reject(error);
}
)
}

Since your getMessage function is async as well you need to wait until all your calls finish.
I would suggest using Promise.all
Here you can find more info: MDN Promise.all()
The code would look something like this:
messageIDs.map(...) returns an array of Promises
use Promise.all() to get an array with all the promises responses
resolve if values are correct reject otherwise
function mainFunction() {
getUnreadMails().then(function(mailArray) {
// Do stuff with the mailArray
// Here it is undefined
})
}
function getUnreadMails() {
return new Promise(function(resolve, reject) {
listMessages(oauth2Client).then(
(messageIDs) => {
return Promise.all(messageIDs.map(id => getMessage(oauth2Client, 'me', id)))
})
.then((messages) => resolve(messages))
.catch(error => reject(error))
});
}
One thing to keep in mind is that Promise.all() rejects if any of your promises failed
Hope this helps!

Explicit construction is an anti-pattern
I believe you can write that piece of code much shorter and, IMHO, cleaner
function mainFunction() {
getUnreadMails().then(function(mailArray) {
// Do stuff with the mailArray
// Here it is undefined
})
}
function getUnreadMails() {
return listMessages(oauth2Client)
.then((messageIDs) => Promise.all(messageIDs.map(id => getMessage(oauth2Client, 'me', id)))
}

Related

What does Promise.all actually do under the hood?

I am trying to understand Promise.all here.
What I did here was to covert below code using Promise.all to achieve the same result.
I understand that Promise all combine the data1, data2.
My question here is that how does Promise.All work without resolve method?
Does Promise resolve those data within the method itself?
Please advise.
const readAllUsersChaining = () => {
return new Promise((resolve, reject) => {
let result = [];
getDataFromFilePromise(user1Path)
.then((data) => {
result.push(JSON.parse(data)); // what are you doing? he's gone mad...
return getDataFromFilePromise(user2Path);
})
.then((data) => {
result.push(JSON.parse(data));
result ? resolve(result) : reject(result);
});
});
};
const readAllUsers = () => {
const data1 = getDataFromFilePromise(user1Path);
const data2 = getDataFromFilePromise(user2Path);
console.log(data1, data2);
return Promise.all([data1, data2]).then((data) => {
return data.map((el) => JSON.parse(el));
});
};
My question here is that how does Promise.All work without resolve method?
Not quite sure what you mean. Promise.all simply creates a new promise internally that is resolved when all other promises are resolved.
Here is a simple implementation of Promise.all for the case that arguments are always promises:
function all(promises) {
if (promises.length === 0) {
return Promise.resolve([]);
}
return new Promise((resolve, reject) => {
const results = [];
let resolved = 0;
promises.forEach((promise, i) => {
promise.then(
result => {
results[i] = result;
resolved++;
if (resolved === promised.length) {
resolve(results);
}
},
error => reject(error)
);
});
}
Promise.all allows to wait until all promise passed as arguments to be fullfilled before the then method attach to be execute.
The real use case would be when you have to perform five call to an API, an you want to perform some treatement only when you have get the data from all the API call. you can rely on the Promise.all function which will wait all passed promised to be fullfilled for it to be fullfiled on it turn.
Bellow I provide an example of a Promise.all call. which has two Promises passed as argument. the first one has a timer which fullfilled it after 5 second and the second if fullfiled immediately but. the Promise.all will be fullfilled only when both of the promise passed as argument ar fullfilled
const firstPromise = new Promise((resolve, reject) => {
setTimeout(()=> {
return resolve({
name: "First Promise"
});
}, 5000);
});
const secondPromise = new Promise((resolve, reject) => {
return resolve({
name: "Second Promise"
});
})
const all = Promise.all([firstPromise, secondPromise]).then((response) => {
console.log(response);
});

Generate an array of promises to run sequentially

I'm trying to generate an array of Promises to run sequentially. I've seen lots of tips on this but can't get it to run in my use case.
export default function generateIcons(){
return new Promise((resolve, reject) => {
const containers = document.querySelectorAll('.html2CanvasTarget')
const promises = containers.map(child => processIcon(child))
promises.reduce((p, fn) => p.then(fn), Promise.resolve())
resolve()
})
}
function processIcon(child){
return new Promise((resolve, reject) => html2canvas(child).
then(canvas => uploadFromCanvas(canvas,
child.childNodes[0].className.split(' ')[1] + '.png'))
.then(resolve).catch(reject))
}
Any tips? This just rejects and I can't see why
Looks like you want to resolve the main promise when the canvases are available for all the child elements. You can use Promise.All() for this.
It should also be noted that the querySelectorAll doesn't return anything you can call the .map on. You will have to create an array from what the querySelectorAll returns - which is a NodeList.
export default function generateIcons(){
return new Promise((resolve, reject) => {
const containers = document.querySelectorAll('.html2CanvasTarget');
const promises = Array.from(containers).map(child => processIcon(child))
Promises.All(promises).then(() => resolve());
})
}
containers is a NodeList, and NodeLists don't have a .map method, which is why your code is throwing an error.
Because processIcon already returns a Promise, there's no need to use the Promise constructor again. html2canvas already returns a Promise too, so there's no need for any Promise constructor anywhere (see What is the explicit promise construction antipattern and how do I avoid it?)
If possible, just await each call of it in a for loop. Because uploadFromCanvas returns a Promise too, and you want to wait for it, return it (or await it) as well:
export default async function generateIcons() {
const containers = document.querySelectorAll('.html2CanvasTarget');
for (const container of containers) {
await processIcon(container);
}
}
function processIcon(child) {
return html2canvas(child, {backgroundColor:null})
.then(canvas => uploadFromCanvas(canvas, child.className.split(' ')[1] + '.png'))
.catch(console.log);
}
As per your question, you can use Q module module for that
You need to take an empty array and push promises into it, and just pass this array in Q method (Q.allSettled), Take a look with an example
const Q = require('q');
const promiseHolder = [];
for (let i = 0; i < 10; i += 1) {
promiseHolder.push('Your Promises');
}
Q.allSettled(promises)
.then((results) => {
results.forEach((result) => {
if (result.state === 'fulfilled') {
const value = result.value;
return value;
}
const reason = result.reason;
throw reason;
});
});
In Q.allSettled() The method you always get the result in .then(). There are 2 states. One for success and one for failure.
Success => state === 'fulfilled', value: 'Whatever your promise return'
Failure => state === 'rejected', reason: 'Whatever your promise thrown'
In this case, you have a number of successful and unsuccessful promises.
There is the second approach which is Promise.all() do the same but the issue is whenever any of promise rejected further promise never called.
const promiseHolder = [];
for (let i = 0; i < 10; i += 1) {
promiseHolder.push('Your Promises');
}
Promise.all(promiseHolder)
.then((results) => {
return results;
})
.catch((err) => {
throw err;
});
In the second approach ( Promise.all()), It consists of all your promises pushed from for loop. If any of promise rejected no more promise called and suddenly you got the state of promise rejection in Promise.all().
Promise.all(promiseHolder)
.then((results) => {
return results;
})
.catch((err) => {
console.log('Promise will reject here', err);
throw err;
});
I hope it helps, Happy Coding :)

Calling async function inside a loop

I have the following function:
function ipfsRetrieve( ipfsHash ){
return new Promise( function( resolve, reject ) {
ipfs.catJSON( ipfsHash, (err, result) => {
if (err){
reject(err);
}
resolve( result);
});
});
}
Now, when I call this function inside a loop as below:
var hashArray = [ "QmTgsbm...nqswTvS7Db",
"QmR6Eum...uZuUckegjt",
"QmdG1F8...znnuNJDAsd6",
]
var dataArray = [];
hashArry.forEach(function(hash){
ipfsRetrieve( hash ).then(function(data){
dataArray.push(data);
});
});
return dataArray
The "return dataArray' line returns an empty array. How should I change this code to have the "dataArray" filled with the data retrived from IPFS?
You should use Promise.all.
Construct an Array of Promises and then use the method to wait for all promises to fulfill, after that you can use the array in the correct order:
let hashArray = ["QmTgsbm...nqswTvS7Db",
"QmR6Eum...uZuUckegjt",
"QmdG1F8...znnuNJDAsd6",
]
// construct Array of promises
let hashes = hashArray.map(hash => ipfsRetrieve(hash));
Promise.all(hashes).then(dataArray => {
// work with the data
console.log(dataArray)
});
For starters, you need to return after rejecting, or else your resolve will get called too.
function ipfsRetrieve( ipfsHash ){
return new Promise( function( resolve, reject ) {
ipfs.catJSON( ipfsHash, (err, result) => {
if (err){
reject(err);
return;
}
resolve( result);
});
});
Now for the loop, use map instead of forEach, and return the promise.
Then wait on the promises.
let promises = hashArry.map(hash=> return new Promise(resolve,reject) { // your code here handling hash, updating theData, and then resolving })
return Promise.all(promises).then( ()=> return theData)
In your case, the promise is provided by ipfsRetrieve, so you would call
let promises = hashArry.map(ipfsRetrieve)
return Promise.all(promises)
The caller of your functions will do this:
ipfsRetrieve().then(data=>{ // process data here } )
If you are cool with async await, do this. (marking containing function as async)
let data = await ipfsRetrieve()

angularjs chaining promises not working , and angular.copy is not either

So my understanding is that i can loop over my FIRST returned set of objects
Object = vm.liberty
Loop
vm.liberty.forEach(function (obj) {
My understanding of this obj is that it is a reference and thus the vm.liberty object will get updated.... right?
I know that console.log is not reliable as it spits out results as soon as line it hit
However
console.log('obj', obj);
That contains the DATA that i want, I have added in a extended nested object to each record in the object ....
But someone either i'm not doing promise chaining correctly or something else
I even try to do a angular.copy
vm.liberty2 = angular.copy(vm.liberty);
That does not work , as console.log(vm.liberty); seems to be empty
Here is my function:
var loadLiberty = function () {
var promise1 = libertyService.getLibertyQuestions();
promise1.then(function (response) {
vm.liberty = response;
}).then(function (res) {
vm.liberty.forEach(function (obj) {
promise2 = libertyService.getDirectives(obj.QuestionId)
.then(function (result) {
obj.directives = result;
console.log('obj', obj);
//vm.liberty.directives = result;
}, function (err) {
console.log('err', err);
});
});
});
//vm.liberty2 = angular.copy(vm.liberty);
console.log(vm.liberty);
vm.liberty2 = angular.copy(vm.liberty);
//return promise1;
};
Thoughts or suggestions ?
Your console.log(vm.liberty); happens before your promises are resolved, that is why it still has its initial value.
Have a look at the following example:
function loadLiberty() {
const libertyQuestionsPromise = libertyService.getLibertyQuestions();
libertyQuestionsPromise
.then(x => vm.liberty = x)
.then(() => {
// hold promises which are getting directives in this array
const promises = [];
vm.liberty.forEach(obj => {
// get directives for every obj
const promise = libertyService
.getDirectives(obj.QuestionId)
.then(x => obj.directives = x);
promises.push(promise);
});
// wait until all promises in the promises array are resolved
return $q.all(promises);
})
.then(() => {
// at this point the vm.liberty should be populated with results
console.log(vm.liberty);
})
.catch(error => {
// handle any error which might have occurred in any of the promises in this chain
});
}
Do not forget to inject the $q dependency in your service/controller.

Bluebird promise loop

I am trying to get all records from a DynamoDB table using promises. The problem is that DynamoDB do not return all items in one call I have to make multiple calls. If LastEvaluatedKey is not null means that I need to make another call with that key to get the remaining records. In my code I am checking that and resolving only after LastEvaluatedKey is null. But the console.log("done") is not being executed.
Here is my code:
function query(params) {
return new Promise(function(resolve, reject) {
docClient.query(params, function(err, data) {
if (err) {
reject(err)
} else {
resolve(data);
}
});
})
}
function getAllRecords(params, combinedData) {
return new Promise(function(resolve, reject) {
query(params)
.then(function(data) {
if(!combinedData) {
combinedData = [];
}
combinedData.push(data.Items);
if(data.LastEvaluatedKey) {
params.ExclusiveStartKey = data.LastEvaluatedKey;
getAllRecords(params, combinedData)
}
else {
resolve(combinedData);
}
})
})
}
getAllRecords(params)
.then(function() {
console.log('done')
})
.catch(function(error) {
console.log(error);
})
It's probably a misconception on how promises work from my part. If someone can give me an idea how to make this work. That would be great.
You've fallen prey to the explicit promise construction antipattern, in which you are manually constructing promises when you don't need to.
Generally the only time you need to use the Promise constructor is when you are converting non-Promise async code to Promise async code. You've already done that in your query() function, so you don't need to use the Promise constructor in your getAllRecords() function.
You should do this instead:
function getAllRecords(params) {
return query(params).then(function (data) {
var items = [data.Items];
if(data.LastEvaluatedKey) {
params.ExclusiveStartKey = data.LastEvaluatedKey;
return getAllRecords(params).then(function (theRest) {
return items.concat(theRest);
});
}
return items;
});
}

Categories