Promise all finishing early when dynamically adding to promises array - javascript

This code works as expected:
services.map(async svc => {
promises.push(new Promise(async (resolve) => {
html += `<h2>${svc}</h2>`;
let journeyDetails = await admin.database().ref(`data`).once('value');
resolve();
}));
});
await Promise.all(promises).then(() => {
return res.send(html);
})
Why does the code below not work? In my eyes it's the same, but the execution order is now incorrect.
Promise.all([
services.map(async svc => {
new Promise(async (resolve) => {
html += `<h2>${svc}</h2>`;
let journeyDetails = await admin.database().ref(`data`).once('value');
resolve();
})
})
]).then(() => {
// done - called before finished in this version
}).catch(() => {
// err
});

I believe the primary reason that your code doesn't work is that you are passing an array of arrays ([services.map(...)]) to Promise.all, not an array of promises.
However, the code is unnecessarily complex. There is no need to create a promise inside an async function, async functions always return a promise. It should just be:
Promise.all( // <- note the removed [...]
services.map(async svc => {
html += `<h2>${svc}</h2>`;
let journeyDetails = await admin.database().ref(`data`).once('value');
// more code here
})
)

Related

Order of promise wrong

I would like to return a list of promises with Promise.all, but the result is always empty.
Here is my theoric code:
function myFunction() {
const pSqlLines = sql.openDatabase().then(mdb => {
const query = `SELECT data FROM database`
return mdb.prepare(query).all()
})
const globalPromise = new Promise( resolve => {
const promises = []
pSqlLines.then( sqlLines => {
sqlLines.forEach(line => {
promiseA()
.then(res1 => { if (res1 == 1) return promiseB() })
.then(res2 => { if (res2 == 1) return promiseC() })
.then(res3 => { if (res3 == 1) promises.push( new Promise(resolve => resolve(line) ) ) })
}) // The promises chaining is correct as the 'promises' array is correctly fulfilled, but later
resolve( Promise.all(promises) ); // It is always called before the foreach loop. Why?
})
})
return globalPromise.then( result => console.log(result) )
}
Someone can help me please?
Thanks a lot.
You should not need to create a new promise with new Promise when you already have a promise to work with -- in your case: pSqlLines. Wrapping pSqlLines inside a new Promise wrapper is an example of the promise constructor antipattern.
Secondly, this code:
new Promise(resolve => resolve(line) )
...can be replaced with:
Promise.resolve(line)
...and since it is to serve as the return value for a then callback, it can be just:
line
As to your question: you are indeed calling Promise.all(promises) at a moment that the promises array is still empty, as the forEach loop has only executed Promise() and its then chain, but none of the asynchronous callbacks that are passed to those then calls have executed yet. You didn't await the resolution of these promises.
I get from your code that you want to exclude some line values depending on conditions. In that case the promise will resolve to undefined. I suppose you would want to exclude those undefined values, and so maybe a filter(Boolean) is appropriate.
Here is a theoretical solution for your theoretical code:
function myFunction() {
const pSqlLines = sql.openDatabase().then(mdb => {
const query = `SELECT data FROM database`;
return mdb.prepare(query).all();
});
return pSqlLines.then( sqlLines => {
const promises = sqlLines.map(line => {
return promiseA()
.then(res1 => { if (res1 == 1) return promiseB(); })
.then(res2 => { if (res2 == 1) return promiseC(); })
.then(res3 => { if (res3 == 1) return line; });
}));
return Promise.all(promises);
}).then(result => result.filter(Boolean));
}

How to make a promise clean up after it has been used

I have an async function that creates a folder and I want to be able to do a computation with this folder and then have the same function remove it.
What I have tried:
const t = async () => {
let createdFolderPath = ''
return new Promise(async (resolve, reject) => {
createdFolderPath = await createAFolder()
console.log('Folder Created')
resolve(createdFolderPath)
}).finally(async () => {
await deleteCreatedFolder(createdFolderPath)
console.log('Deleted Folder')
})
}
t().then(async (folderpath) => {
await doSomethingWithFolderThatIsAsync(folderPath)
console.log('Computation Done')
})
What I want to happen is:
Folder Created
Computation Done
Deleted Folder
What actually happens is:
Folder Created
Deleted Folder
Computation Done
I want the t function to also remove the folder so the person using it doesn't have to worry about removing the folder.
I'd pass the .then callback into t instead of using the returned Promise from t. You should also avoid the explicit Promise construction antipattern:
const t = async (thenCallback) => {
let createdFolderPath = ''
return createAFolder()
.then(thenCallback)
.finally(async () => {
await deleteCreatedFolder(createdFolderPath)
console.log('Deleted Folder')
})
}
t(async (folderpath) => {
await doSomethingWithFolderThatIsAsync(folderPath)
console.log('Computation Done')
})
.then(() => {
// everything finished
});

I'm trying to use async/await to get a service, but the second service returns don't fill my variables

I have a service to get a list from server. But in this list I need to call another service to return the logo img, the service return ok, but my list remains empty. What i'm did wrong ?
I tried to use async/await in both services
I tried to use a separate function to get the logos later, but my html don't change.
async getOpportunitiesByPage(_searchQueryAdvanced: any = 'active:true') {
this.listaOportunidades = await this._opportunities
.listaOportunidades(this.pageSize, this.currentPage, _searchQueryAdvanced)
.toPromise()
.then(result => {
this.totalSize = result['totalElements'];
return result['content'].map(async (opportunities: any) => {
opportunities.logoDesktopUrl = await this.getBrand(opportunities['brandsUuid']);
console.log(opportunities.logoDesktopUrl);
return { opportunities };
});
});
this.getTasks(this.totalSize);
}
No errors, just my html don't change.
in my
console.log(opportunities.logoDesktopUrl);
return undefined
but in the end return filled.
info:
Angular 7
server amazon aws.
await is used to wait for promise.
You should return promise from getBrand if you want to wait for it in getOpportunitiesByPage.
Change the getBrand function as following.
getBrand(brandsUuid): Observable<string> {
this.brandService.getById(brandsUuid).pipe(map(res => {
console.log(res.logoDesktopUrl); return res.logoDesktopUrl;
}))
}
Change opportunities.logoDesktopUrl = await this.getBrand(opportunities['brandsUuid']); to opportunities.logoDesktopUrl = await this.getBrand(opportunities['brandsUuid']).toPromise();
Please make sure you imported map from rxjs/operators.
At first,when you await, you should not use then.
At second, async/await runs only with Promises.
async getOpportunitiesByPage(_searchQueryAdvanced: any = 'active:true') {
const result = await this._opportunities
.listaOportunidades(this.pageSize, this.currentPage, _searchQueryAdvanced)
.toPromise();
this.totalSize = result['totalElements'];
this.listaOportunidades = result['content'].map(async (opportunities: any) => {
opportunities.logoDesktopUrl = await this.getBrand(opportunities['brandsUuid']);
console.log(opportunities.logoDesktopUrl);
return opportunities;
});
this.getTasks(this.totalSize);
}
getBrand(brandsUuid) {
return new Promise((resolve, reject) => {
this.brandService.getById(brandsUuid).subscribe(res => {
console.log(res.logoDesktopUrl);
return resolve(res.logoDesktopUrl);
}, err => {
return reject(err);
});
});
}
But, because rxjs is a used in Angular, you should use it instead of async/await :
getOpportunitiesByPage: void(_searchQueryAdanced: any = 'active:true') {
this._opportunities.listaOportunidades(this.pageSize, this.currentPage, _searchQueryAdvanced).pipe(
tap(result => {
// we do that here because the original result will be "lost" after the next 'flatMap' operation
this.totalSize = result['totalElements'];
}),
// first, we create an array of observables then flatten it with flatMap
flatMap(result => result['content'].map(opportunities => this.getBrand(opportunities['brandsUuid']).pipe(
// merge logoDesktopUrl into opportunities object
map(logoDesktopUrl => ({...opportunities, ...{logoDesktopUrl}}))
)
),
// then we make each observable of flattened array complete
mergeAll(),
// then we wait for each observable to complete and push each result in an array
toArray()
).subscribe(
opportunitiesWithLogoUrl => {
this.listaOportunidades = opportunitiesWithLogoUrl;
this.getTasks(this.totalSize);
}, err => console.log(err)
);
}
getBrand(brandsUuid): Observable<string> {
return this.brandService.getById(brandsUuid).pipe(
map(res => res.logoDesktopUrl)
);
}
Here is a working example on stackblittz
There might be a simpler way to do it but it runs :-)

Returning a promise when using async/await in Firebase Cloud Functions

So I've happily been using async/await since node 8 is supported on Firebase Cloud Functions. I am struggling with 1 thing though. When using callable functions, it is told that you have to return a promise in the function, otherwise it won't work correctly. When using raw promises, its clear to me how to use it:
exports.createBankAccount = functions.region('europe-west1').https.onCall((data, context) => {
return promiseMethod().then((result) => {
return nextPromise(result);
}).then((result) => {
return result;
}).catch((err) => {
// handle err
})
});
But now, with async await, I'm not sure how to return this "chain of promises":
exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) => {
const res1 = await promiseMethod();
const res2 = await nextPromise(res1);
return res2;
// ??? Where to return the promise?
});
Does somebody know?
HTTP functions don't return a promise. They just send a result. You still have to use promises correctly in order to send the result, but a return value is not required. HTTP functions are terminated when the response is sent. See the documentation for more details:
Terminate HTTP functions with res.redirect(), res.send(), or res.end().
"await" is just syntax sugar for returning a Promise
When you write an async function, the code will actually exit the function and return a Promise at the first await it encounters. All code after the await will be converted to a then().
So for firebase writing code with async/await is perfectly save and in my experience even less error-prone, since I can more easily structure try&catch in my code!
Proof:
Just run this in your console:
async function iAmAsync() {
await new Promise(r => window.setTimeout(r, 1000))
return 'result'
}
let x = iAmAsync()
console.log(x)
Will print: Promise{<resolved>: "result"}
TL;DR: You don't need to change anything - if you write code with multiple awaits, this will be handled by firebase like a chain of promises and everything will just work.
And since my answer was downvoted, here is an authorative code-sample by the google firebase team itself:
https://github.com/firebase/functions-samples/blob/master/quickstarts/uppercase/functions/index.js
exports.addMessage = functions.https.onRequest(async (req, res) => {
// [END addMessageTrigger]
// Grab the text parameter.
const original = req.query.text;
// [START adminSdkPush]
// Push the new message into the Realtime Database using the Firebase Admin SDK.
const snapshot = await admin.database().ref('/messages').push({original: original});
// Redirect with 303 SEE OTHER to the URL of the pushed object in the Firebase console.
res.redirect(303, snapshot.ref.toString());
// [END adminSdkPush]
});
You nailed it with your example code.
Async/await is just a newer way of promise. They can be used interchangeable.
Here is an example promise and async/await of the same function.
This
exports.createBankAccount = functions.region('europe-west1').https.onCall((data, context) => {
return promiseMethod().then((result) => {
return nextPromise(result);
}).catch((err) => {
// handle error here
})
});
is equivalent to this:
exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) => {
try {
const result = await promiseMethod();
return nextPromise(result); // You are returning a promise here
}catch(e) {
// handle error here
}
});
Note that in both cases, you are returning a promise at the end. The return value of this onCall function would be whatever nextPromise(result) is. Since you are returning nextPromsie(result), you don't need to await it.
To see the code solution to your question look at the answer of dshukertjr.
If you want to understand how to return a "chain of promises" with async/await, here is your answer:
You cant !
Why ? Because await is used to wait for a Promise to complete. Once await return a value their is no more Promise.
So if you absolutely want to return a promise using await, you can wait for one of the two functions that return promises but not both.
Here is two way to do that:
A :
exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) => {
try {
const result = await promiseMethod();
return nextPromise(result); // You are returning a promise here
}catch(e) {
// handle error here
}
});
B:
exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) => {
return promiseMethod().then(async (result) => {
return await nextPromise(result);
}).catch((err) => {
// handle err
})
});
The only difference between A and B is that A waits for "PromiseMethod" to complete before returning a Promise. Whereas B returns a Promise right after being called.
Seems, we have to wait for several Promises in way like this:
const listOfAsyncJobs = [];
listOfAsyncJobs.push(createThumbnail(1, ...));
listOfAsyncJobs.push(createThumbnail(2, ...));
listOfAsyncJobs.push(createThumbnail(3, ...));
...
return Promise.all(listOfAsyncJobs); // This will ensure we wait for the end of the three aync tasks above.
From async method whatever you return it gets wrapped in promise automatically.
e.g
const myFun = async () => {return 5}
myFun();
// Output in the console
Promise {<fulfilled>: 5}
And you can chain with the returned result since it is a promise
Another example with enhancement as suggested in other answer
const myFun4 = async () => {
const myNum = await new Promise(r => window.setTimeout(() => r(5), 1000));
const myNum2 = await new Promise(r => window.setTimeout(() => r(5), 1000));
return myNum + myNum2;
}
myFun4().then((n) => console.log(n));
// Output
10
The return value of async-await function is Promise.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function#return_value
So, what you did actually is returning a chain of promises.
const nextPromise = () => {
console.log('next promise!');
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('next promise result')
}, 3000)
});
}
const promiseMethod = () => {
console.log('promise!');
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('promise result');
}, 2000)
});
}
exports.createBankAccount = functions.https.onCall((data, context) => {
return promiseMethod().then((result) => {
return nextPromise(result);
}).then((result) => {
return result;
}).catch((err) => {
// handle err
console.log(err);
})
});
exports.createBankAccountAsync = functions.https.onCall(async (data, context) => {
const result = await promiseMethod();
const res = await nextPromise(result);
return res;
});
I have created test project on firebase and both function calls give same logs.
A solution in that case is Promise.all().
exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) => {
const promises = [];
const res1 = await promiseMethod();
const res2 = await nextPromise(res1);
promises.push(res1);
promises.push(res2);
// Here's the return of the promises
return Promise.all(promises).catch(error => console.error(error));
});
You may find more informations about promises in this article on freecodecamp.org/promise-all
Solution
For an alternative method, you can use Promise.allSettled(). It is the best way to wait for all promises in the function to complete as well as provide an easy way to modify the final return.
Excerpt from the documentation
The Promise.allSettled() method returns a promise that resolves after all of the given promises have either fulfilled or rejected, with an array of objects that each describes the outcome of each promise.
It is typically used when you have multiple asynchronous tasks that are not dependent on one another to complete successfully, or you'd always like to know the result of each promise.
Your updated code should be
exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) => {
const res1 = await promiseMethod();
const res2 = await nextPromise(res1);
return Promise.allSettled([res1, res2]).then((results) => results.forEach((result) => console.log(result.status)));
});
Additional Info
You should also look into Promise object, it has some nice methods for such a situation. Read more at documentation link
Since you need to return promise you can create the promise object and resolve/reject (return) your response from api after processing all the promises.
Option 1:
exports.createBankAccount = functions.region('europe-west1').https.onCall((data, context) => {
return new Promise(async (resolve, reject) => {
try {
const res1 = await promiseMethod();
const res2 = await nextPromise(res1);
// This will return response from api
resolve(res2);
}
catch (err) {
// Handle error here
// This will return error from api
reject(err)
}
})
});
Option 2:
exports.createBankAccount = functions.region('europe-west1').https.onCall((data, context) => {
return new Promise(async (resolve, reject) => {
const res1 = await promiseMethod();
const res2 = await nextPromise(res1);
// This will return response from api
resolve(res2);
})
.then((val) => val)
.catch((err) => {
// Handle error here
// This will return error from api
return err
})
});
Just convert to a Promise if required.
I.e. If nextPromise returns a Promise:
exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) => {
const res1 = await promiseMethod();
return nextPromise(res1);
});
On the other hand, if nextPromise is an async function, just convert it to a Promise:
exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) => {
const res1 = await promiseMethod();
return Promise.resolve(nextPromise(res1));
});
you can also convert the result:
exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) => {
const res1 = await promiseMethod();
const res2 = await nextPromise(res1);
return Promise.resolve(res2);
});

How to structure nested Promises

I have a situation where I think the only choice for me is to nest some Promises within each other. I have a Promise that needs to be performed and a method that does something until that Promise is complete. Something like this:
let promise = new Promise((resolve, reject) => {
// Do some stuff
});
doSomethingUntilPromiseisDone(promise);
However, within my Promise, I need to execute another method that returns another Promise:
let promise = new Promise((resolve, reject) => {
fetchValue(url)
.then((value) => {
// Do something here
}).catch((err) => {
console.error(err);
});
});
doSomethingUntilPromiseisDone(promise);
But now, in the fetchValue method's then statement, I have another method I need to execute that, guess what, returns another Promise:
let promise = new Promise((resolve, reject) => {
fetchValue(url)
.then((value) => {
saveToCache(value)
.then((success) => {
console.log('success!!');
resolve('success');
});
}).catch((err) => {
console.error(err);
});
});
doSomethingUntilPromiseisDone(promise);
So in the end, I have a Promise, within a Promise, within a Promise. Is there someway I can structure this better so that it is more straightforward? It seems like nesting them within each other is counter to Promise's intended chaining approach.
Use .then()
let doStuff = (resolve, reject) => {/* resolve() or reject() */};
let promise = new Promise(doStuff);
doSomethingUntilPromiseisDone(
promise
.then(value => fetchValue(url))
.then(value => value.blob())
.then(saveToCache)
)
.then(success => console.log("success!!"))
.catch(err => console.error(err))
you can use generator to flatten your nested promises (Bluebird.couroutine or Generators)
//Bluebird.couroutine
const generator = Promise.coroutine(function*() {
try {
const value = yield fetchValue(url);
const success = yield saveToCache(value);
console.log('success:', success);
} catch(e) {
console.error(err);
}
}));
generator();
Each function will call the next one with the result of the method before.
var promises = [1,2,3].map((guid)=>{
return (param)=> {
console.log("param", param);
var id = guid;
return new Promise(resolve => {
// resolve in a random amount of time
setTimeout(function () {
resolve(id);
}, (Math.random() * 1.5 | 0) * 1000);
});
}
}).reduce(function (acc, curr, index) {
return acc.then(function (res) {
return curr(res[index-1]).then(function (result) {
console.log("result", result);
res.push(result);
return res;
});
});
}, Promise.resolve([]));
promises.then(console.log);

Categories