I'm having a generator that, among other operations, queries a database, like
function* current(db) {
const items = await db.collection('...').find({ ... });
for (const item of items)
if (...) yield item;
}
which isn't valid syntax. Using promises and yielding from a then, isn't possible either.
What should I do then? How can I use asynchronous operation inside a generator?
Calling await in generators (aka async generators) has been supported natively in Node v10+ (released April 2018) and Chrome since v63, as well as Firefox v57.
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function getAsyncData() {
await sleep(1000); // simulate database/network delay...
return [1, 2, 3, 4, 5]; // ...then return some data
}
// async generator
async function* filterAsyncData() {
const items = await getAsyncData(); // await call in generator
for (const item of items)
if (item % 2) yield item;
}
(async function main() {
// for-await syntax
for await (const filteredItem of filterAsyncData())
console.log(filteredItem);
// classic iterator syntax
const fadIt = filterAsyncData();
while (true) {
const result = await fadIt.next(); // note await
if (result.done) break;
console.log(result.value);
}
}());
Compared to a regular (non-async) generators, remember to use await before calling .next(). Alternatively, the new for-await-of syntax has been available since Node v9.2, and can be used in Node v10 without any flags.
For older versions of node (or for Jest), you can use the Babel plugin transform-async-generator-functions.
You can't combine async with a generator function. That said you most likely don't want to be using a generator in your case and instead just have an async function:
async function current(db) {
while (true) {
const latest = await db.collection('messages').findOne({}, {
sort: {
timestamp: -1
}
});
return smth;
}
}
The generator will be generated for you when the async is transpiled.
Related
I am doing an Image Upload feature with Cloudinary. I'm providing an array which may contains base64coded or uploaded image which is a url :
[
"https://res.cloudinary.com/\[userName\]/image/upload/v167xxxx4/luxxxfsgasxxxxxx7t9.jpg", "https://res.cloudinary.com/doeejabc9/image/upload/v1675361225/rf6adyht6jfx10vuzjva.jpg",
"data:image/jpeg;base64,/9j/4AAUSkZJRgABAQEBLAEsAA.......", "data:image/jpeg;base64,/9j/4AAUSkZJRgABAQEBLAEsAA......."
]
I'm using this function to upload the "un-uploaded", which returns the all uploaded version:
export async function uploadImage(el: string[]) {
const partition = el.reduce(
(result: string[][], element: string) => {
element.includes("data:image/")
? result[0].push(element)
: result[1].push(element);
return result;
},
[[], []]
);
for (let i = 0; i < partition[0].length; i++) {
const data = new FormData();
data.append("file", partition[0][i]);
data.append("upload_preset", "my_preset_name");
const res = await fetch(
"https://api.cloudinary.com/v1_1/userName/image/upload",
{
method: "POST",
body: data,
}
);
const file = await res.json();
partition[1].push(file.secure_url);
console.log(partition[1]);
}
return partition[1];
}
Then I will use the return value to update the state and call the api to update database:
const uploaded = await uploadImage(el[1])
console.log(uploaded);
setFinalVersionDoc({
...chosenDocument,
[chosenDocument[el[0]]]: uploaded,
});
However, it always updates the useState before the console.log(uploaded). I thought async/await would make sure the value is updated before moving on.
The GitHub repo is attached for better picture. The fragment is under EditModal in the 'component/document' folder:
https://github.com/anthonychan1211/cms
Thanks a lot!
I am hoping to make the upload happen before updating the state.
The function is correct, but you are trying to await the promise inside the callback function of a forEach, but await inside forEach doesn't work.
This doesn't work:
async function handleEdit() {
const entries = Object.entries(chosenDocument);
entries.forEach(async (el) => { // <------ the problem
if (Array.isArray(el[1])) {
const uploaded = await uploadImage(el[1]);
el[1].splice(0, el[1].length, uploaded);
}
});
[...]
}
If you want to have the same behaviour (forEach runs sequentially), you can use a for const of loop instead.
This works (sequentially)
(execution order guaranteed)
async function handleEdit() {
const entries = Object.entries(chosenDocument);
for (const el of entries) {
// await the promises 1,2,...,n in sequence
if (Array.isArray(el[1])) {
const uploaded = await uploadImage(el[1]);
el[1].splice(0, el[1].length, uploaded);
}
}
}
This also works (in parallel)
(execution order not guaranteed)
async function handleEdit() {
const entries = Object.entries(chosenDocument);
await Promise.all(entries.map(async (el) => {
// map returns an array of promises, and await Promise.all() then executes them all at the same time
if (Array.isArray(el[1])) {
const uploaded = await uploadImage(el[1]);
el[1].splice(0, el[1].length, uploaded);
}
}));
[...]
}
If the order in which your files are uploaded doesn't matter, picking the parallel method will be faster/better.
const fetchPrice = async (symbols) => {
const prices = {};
await Promise.all(
symbols.map(async (symbol) => {
const priceInformation =
(base.id, await base.fetch_ticker((Symbol = replacedSymbol)));
prices[symbol] = priceInformation;
})
);
return prices;
};
const PriceObj = await fetchPrice(SYMBOL_LIST);
console.log(PriceObj);
In the above code, I don't know how to wait for fetchPrice to be processed before executing console.log();.
The console.log(); only returns unexpected values, such as undefined or Promise.
(If I put console.log(prices); before return at the end of the fetchPrice function, then only this console.log(); returns the expected value, so the fetchPrice function itself is working).
I saw some information somewhere that return new Promise should be used, but it was too complicated for a beginner to understand.
How can I wait for fetchPrice to process and then display the last console.log(PriceObj); value correctly?
Here's a runnable version of your code where I've simulated fetch_ticker() as an asynchronous function that returns a promise that resolves in a random amount of time and modified some things in your code that seemed odd or incorrect.
function randInt(min, max) {
const r = Math.random();
return Math.floor((r * (max - min)) + min);
}
function delay(t, v) {
return new Promise(resolve => setTimeout(resolve, t, v));
}
// simulate base.fetch_ticker
// resolves in a random amount of time with a random integer value
const base = {
fetch_ticker() {
let t = randInt(100, 1000);
return delay(t, t);
}
}
const fetchPrice = async (symbols) => {
const prices = {};
await Promise.all(symbols.map(async (symbol) => {
const priceInformation = await base.fetch_ticker(symbol);
prices[symbol] = priceInformation;
}));
return prices;
};
async function run() {
const SYMBOL_LIST = ["IBM", "GM", "TSLA", "GOOG"];
const PriceObj = await fetchPrice(SYMBOL_LIST);
console.log(PriceObj);
}
run().then(result => {
console.log("done");
}).catch(err => {
console.log(err);
});
As you can see in my comments to your question, there were parts of your code that didn't make sense to me. It is unclear what you're trying to do with the (Symbol = replacedSymbol) part of this since Symbol is a built-in global and replacedSymbol is not shown in your code at all and it's unclear why you would be doing this assignment in the middle of passing arguments:
base.fetch_ticker((Symbol = replacedSymbol))
And, it's unclear why the base.id is involved in this:
const priceInformation =
(base.id, await base.fetch_ticker((Symbol = replacedSymbol)));
This statement:
(base.id, await base.fetch_ticker((Symbol = replacedSymbol)))
will just evaluate to the the value of the second item in the comma list and the base.id will have no effect at all.
In my code example above, I've removed both of those elements since there's no explanation or reasoning provided for them.
Am I missing an integrated functionality of the dart language (or a lightweight library) to do what where() does on iterables, only with an async test? I'm currently using my own implementation:
Future<Iterable<T>> whereAsync<T>(
Iterable<T> collection, Future<bool> Function(T) asyncTest) async {
Map<T, bool> mappedCollection = Map();
await Future.wait(collection.map((element) async =>
mappedCollection[element] = await asyncTest(element)));
return mappedCollection.entries
.where((entry) => entry.value)
.map((entry) => entry.key);
}
I think you code example looks a more complicated than it needs to be. Also, you should try read something about Streams since they are the async version of iterator.
I have tried to make my own version of you code which I think solves the same problem:
import 'dart:async';
void main() {
final list = [1, 2, 3];
whereAsync(Stream.fromIterable(list), (e) => e != 2).forEach(print); // 1 3
}
Stream<T> whereAsync<T>(Stream<T> stream, FutureOr<bool> check(T input)) async* {
await for (var element in stream) {
if (await check(element)) {
yield element;
}
}
}
Since the code posed in the question indexes into a map using the elements of the Iterable, it will suffer from a potential defect when two or more elements are equal.
Here's a method which is simpler and doesn't exhibit the potential defect.
class Pair<T1, T2> {
final T1 first;
final T2 second;
Pair(this.first, this.second);
}
Future<Iterable<T>> whereAsync<T>(Iterable<T> items, Future<bool> Function(T) test) async {
final futures = items.map((e) => test(e).then((v) => Pair(e, v)));
final results = await Future.wait(futures);
return results.where((e) => e.second).map((e) => e.first);
}
So, I have a map which has to do with some asynchronous processing using the items inside. I used the forEach loop construct and inside the callback is designed to be async because I call an await inside the iteration body
myMap.forEach((a, b) { await myAsyncFunc(); } );
callFunc();
I need the callFunc() to be called after all the items have been iterated. But the forEach exits immediately. Help!
Use a for loop over Map.entries instead of forEach. Provided you are in an async function, await-ing in the body of a for loop will pause the iteration. The entry object will also allow you to access both the key and value.
Future<void> myFunction() async {
for (var entry in myMap.entries) {
await myAsyncFunction(entry.key, entry.value);
}
callFunc();
}
You can also use Future.forEach with a map like this :
await Future.forEach(myMap.entries, (MapEntry entry) async {
await myAsyncFunc();
});
callFunc();
You could also use map like:
const futures = myMap.map((a, b) => myAsyncFunc());
await Future.wait(futures);
callFunc();
entries used in extract Maplist from HashMap.
products.entries.forEach((e) {
var key = e.key;
var values = e.value;
double sum = 0;
values.forEach((value) => sum += value[PlanogramShelf.SHELF_FULL]);
target.add(OrdinalSales(
key, double.parse((sum / valueslength).toStringAsFixed(2))));
});
async/await doesn't seem to work with firebase forEach. This code runs console.log for the first child of the snapshot and then just hangs. Is this a bug or am I missing something?
main()
async function main() {
const snap = await root.ref('somepath')
.once('value')
snap.forEach(async val => {
await console.log(val.key)
})
}
It's an unintentional consequence of a feature, sometimes knows as a bug. From the firebase documentation:
an async function always returns a Promise. And a promise is true-y value:
!!Promise.resolve===true.
As a workaround, try this solution:
main()
async function main() {
const snap = await root.ref('somepath')
.once('value')
snap.forEach(function wrapper(){async val => {
await console.log(val.key)
}})
It won't wait for the promise to complete before moving to the next snapshot though. That would require a bit more code, but that may not be what you need anyways.
I use something like this:
import * as admin from "firebase-admin";
type FunctionOnDataSnapshot = (childSnapshot: admin.database.DataSnapshot) => Promise<any>;
export const asyncForEach = async (dataSnapshot: admin.database.DataSnapshot, childFunction: FunctionOnDataSnapshot) => {
const toWait = [];
dataSnapshot.forEach(childSnapshot => {
toWait.push(childFunction((childSnapshot)));
});
await Promise.all(toWait);
};