Implement for-await-of statement in RxJS - javascript

I have the following statement:
for await (const blob of client.list()) {
console.log('\t', blob.name);
}
client.list() returns an async iterable iterator, and expects the use of for await...of to resolve the promises. I would like to incorporate the code into an existing rxjs pipe that instantiates the client.
I looked everywhere and I couldn't figure out how to do so without resolving the promise inside the pipe rather than converting into observables.
Any help would be appreciated!

I can't find an existing rxjs operator, but it doesn't seem too difficult to make your own. When integrating other APIs with observables you can interact with the API within the function passed to the observable constructor. This allows a lot of flexibility when triggering next/error/complete.
Edit - I've added a second option for doing this, using rxjs operators and avoiding explicitly calling next/error/complete.
const {
Observable,
operators,
from
} = rxjs;
const {take, takeWhile, expand, map, filter} = operators;
const asyncGen = async function*(x = -1) {
while(x++ < 5) {
yield x;
}
};
const fromAsyncIter = iterable => new Observable(subscriber => {
let unsubscribed = false;
const iterate = async () => {
try {
for await (let n of iterable) {
console.log('await', n);
subscriber.next(n);
if (unsubscribed) return;
}
subscriber.complete();
} catch (e) {
subscriber.error(e);
}
}
iterate();
return () => unsubscribed = true;
});
const fromAsyncIter2 = iterable =>
from(iterable.next()).pipe(
expand(() => iterable.next()),
takeWhile(x => !x.done),
map(x => x.value)
);
// const source = fromAsyncIter(asyncGen()).pipe(take(2));
const source = fromAsyncIter2(asyncGen()).pipe(take(2));
source.subscribe({
next: x => console.log('next', x),
error: e => console.error(e),
complete: () => console.log('complete')
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.4/rxjs.umd.js"></script>

Related

using async await with reduce

i have following
const imageField = ["logoUrl", "fullLogoUrl"]
const onCreate = async (submitData: any) => {
const uploadImageField = await imageField.reduce(
async function (acc: any, cur: string) {
await acc;
const url = await uploadImage(submitData.general[cur][0]);
acc[cur] = url;
return acc;
},
{}
);
console.log(uploadImageField);
}
this is my console.log
{
logoUrl: "https://........"
}
only logoUrl field is show, fullLogoUrl is missing
The problem is that acc on the second iteration is a promise object - that's why you await it. However, you still assign the [cur] property on that promise object, not on the promise result, and the implicit promise chaining of the async function as well as the explicit awaits will just ignore properties on the promise object. You could fix this by doing acc = await acc;, but really I recommend not to use reduce with async/await at all. A normal loop is much simpler and has no pitfalls.
const imageField = ["logoUrl", "fullLogoUrl"]
const onCreate = async (submitData: any) => {
const uploadImageField = {};
for (const cur of imageField) {
const url = await uploadImage(submitData.general[cur][0]);
acc[cur] = url;
}
console.log(uploadImageField);
}

Override function prototype with async method, keeping this context

In javascript if i have a function defined like so
function Person() {
}
Person.prototype.someFunction = function() {
// Does soome logic
return this
}
Person.prototype.anotherFunction = function () {
// Does soome logic
return this;
};
And i want to implement chaining i will do something like this
const person = new Person();
person.someFunction().anotherFunction()
And this works as each method returns the instance of Person.
Now if i have a method which has some async action how do i return the this instsance in an async method
function someApiCall() {
return new Promise((res) => {
setTimeout(() => {
res('got data');
}, 2000);
});
}
Person.prototype.asyncFunction = function () {
someApiCall()
.then()
.catch()
// HOW DO I RETURN THIS INSTANCE HERE ???? as someAPICALL is async
};
So that i can use it as
person.someFunction().asyncFunction().anotherFunction()
Option 1 (Not executed in order):
Person.prototype.asyncFunction1 = function () {
someApiCall()
.then((e) => console.log(e))
.catch()
return this;
};
p.anotherFunction().asyncFunction1().anotherFunction()
All functions get called, but not in order. If you want to execute it in order, just do it like this:
Option 2 (Executed in order):
Person.prototype.asyncFunction2 = async function () {
const ans = await someApiCall();
console.log(ans);
return this;
};
// t represents this as you return it in asyncFunction2
p.anotherFunction().asyncFunction2().then((t) => t.anotherFunction())
You're trying to apply a synchronous programming paradigm to an asynchronous code approach, and that's just not going to work. When working with promises, which is what async does for you in an automatic way, rather than instance chaining, your code logic now needs to deal with promise chaining instead.
First, let's stop using legacy prototype syntax and look at modern (where "modern" is over five years old by now) class syntax:
class Person {
async someFunction() {
return ...
}
async anotherFunction() {
return ...
}
}
Because async is just a convenient promise wrapping, we have two options:
const person = new Person();
person
.someFunction()
.then(result => {
person
.anotherFunction()
.then(result => ...);
.catch(e => console.error(e));
})
.catch(e => console.error(e));
but this is both cumbersome and ugly. Let's use awaits instead:
const person = new Person();
try {
const someResult = await person.someFunction();
const anotherResult = await person..anotherFunction();
...
} catch (e) {
console.error(e);
}
Much better. We don't need instance chaining anymore when we're using async patterns, it's a pattern from a previous era of JS, and writing modern code does not benefit from trying to force it back in.
Some people are telling you that it can never be done or it's just not going to work. It's not their fault for misunderstanding but you don't need to suffer the same way as them.
Let' say you have an ordinary class, Account, with a few async methods -
class Account {
constructor(balance) {
this.balance = balance
}
async withdraw (x) {
await sleep(1000)
this.balance -= x
}
async deposit (x) {
await sleep(1000)
this.balance += x
}
}
sleep is a simple function which delays the program for ms milliseconds -
const sleep = ms =>
new Promise(r => setTimeout(r, ms))
Now we can write a chain function -
const chain = t =>
new Proxy(Promise.resolve(t), { get: get(t) })
const get = t => (target, prop) => (...args) =>
prop === "then"
? target[prop](...args)
: chain(target.then(async v => (await v[prop](...args), v)))
This seemingly allows us to mix synchronous and asynchronous behaviour -
const A = new Account(100)
const B = new Account(200)
chain(A).deposit(5).withdraw(20).then(a => console.log("A", a))
chain(B).withdraw(20).withdraw(30).deposit(10).then(b => console.log("B", b))
Run the snippet below to verify the result in your own browser -
const sleep = ms =>
new Promise(r => setTimeout(r, ms))
const get = t => (target, prop) => (...args) =>
prop === "then"
? target[prop](...args)
: chain(target.then(async v => (await v[prop](...args), v)))
const chain = t =>
new Proxy(Promise.resolve(t), { get: get(t) })
class Account {
constructor(balance) {
this.balance = balance
}
async withdraw (x) {
await sleep(1000)
this.balance -= x
}
async deposit (x) {
await sleep(1000)
this.balance += x
}
}
const A = new Account(100)
const B = new Account(200)
chain(A).deposit(5).withdraw(20).then(a => console.log("A", a))
chain(B).withdraw(20).withdraw(30).deposit(10).then(b => console.log("B", b))
console.log("running...")
A { balance: 85 }
B { balance: 160 }
Invent your own convenience. It's your program to do with as you please.

JS/React - How should I use a fetch call inside of a for loop?

I'm still a little unfamiliar with asynchronous functions beyond their simplest forms, so I'm hoping someone here can help me out.
Some information:
getCollection() returns an array of objects that look like this.
{
"id": 1,
"userId": 3,
"gameId": 3498,
"review": "Testing review for game 1"
},
getGameById() takes in a integer (the id of a game) and returns that game object from an external API.
gameArray should be getting filled with game objects whose IDs in the external API match the IDs from the array of objects provided by getCollection.
const [games, setGames] = useState([])
const getGames = () => {
getCollection().then(userGames => {
let gameArray = []
for (const eachObj of userGames) {
if (eachObj.userId === currentUserId) {
getGameById(eachObj.gameId).then(game => {
gameArray.push(game)
})
}
}
Promise.all(gameArray).then(() => {
console.log(gameArray) //logs empty array, but shouldn't be empty
setGames(gameArray)
})
})
}
I have never used Promise.all before, it's just something I saw as a possible solution to this issue I'm having.
Promise.all takes an array of promises.
First you must build the array of promises. Then you must call Promise.all with this array to retrieve all the games :
function getGames() {
getCollection().then(userGames => {
const gamePromises = userGames
.filter(userGame => userGame.userId == currenUserId)
.map(userGame => getGameById(userGame.gameId));
Promise.all(gamePromises).then(games=> {
console.log(games);
setGames(games)
});
})
}
Here is another solution using async function which is maybe more readable
async function getGames() {
const userGames = await getCollection();
const currentUserGames = userGames.filter(({userId}) => userId == currentUserId);
const games = await Promise.all(userGames.map(({gameId}) => getGameById(gameId));
setGames(games);
}
The array that you pass to Promise.all needs to contain the promises, but you're pushing the game objects - and you're doing it asynchronously, so the array is still empty when you pass it to Promise.all.
const getGames = () => {
getCollection().then(userGames => {
let gamePromises = []
for (const eachObj of userGames) {
if (eachObj.userId === currentUserId) {
gamePromises.push(getGameById(eachObj.gameId))
// ^^^^^^^^^^^^^^^^^^ ^
}
}
return Promise.all(gamePromises)
// ^^^^^^ chaining
}).then(gameArray => {
// ^^^^^^^^^
console.log(gameArray)
setGames(gameArray)
})
}
To simplify:
async function getGames() {
const userGames = await getCollection()
const gamePromises = userGames
.filter(eachObj => eachObj.userId === currentUserId)
.map(eachObj => getGameById(eachObj.gameId))
const gameArray = await Promise.all(gamePromises)
console.log(gameArray)
setGames(gameArray)
}

Async/Await inside the Observable

How can I use async/await inside the Observable??
With this code I'm unable to trigger the unsubscribe function within observable thus interval is not cleared.
const { Observable } = require("rxjs");
const test = () => new Observable(async (subscriber) => {
await Promise.resolve();
const a = setInterval(() => {
subscriber.next(Math.random());
console.log("zz");
}, 500);
return () => {
console.log("asdsad");
clearInterval(a);
};
});
const xyz = test().subscribe(console.log);
setTimeout(() => {
xyz.unsubscribe();
}, 3000);
Async/Await inside an observable is not supported. However, it can be done with a behavior subject and an asynchronous nested function.
Create a behavior subject, convert it to an observable (.asObservable()), execute the asynchronous nested function, return the observable. Here's an example.
function getProgress() {
// Change this value with latest details
const value = new BehaviorSubject('10%');
const observable = value.asObservable();
// Create an async function
const observer = async() => {
// Perform all tasks in here
const wait1 = await new Promise(resolve => setTimeout(resolve, 3000));
value.next('66%');
const wait2 = await new Promise(resolve => setTimeout(resolve, 3000));
value.next('100%');
// Complete observable
value.complete();
}
// Call async function & return observable
observer();
return observable;
}
It's very readable and works like a charm.
First of all, subscriber passed to observable contructor cannot be async function. There is no support for that.
If you need to create observable from promise, use from:
import { from } from 'rxjs';
const observable = from(promise);
But considering your scenario.
Because there is no way to cancel native js promise, you cannot realy unsubscribe from such created observable, so:
const obs = from(new Promise(resolve => {
setTimeout(() => {
console.log('gonna resolve');
resolve('foo');
}, 1000);
}));
const sub = obs.subscribe(console.log);
setTimeout(() => sub.unsubscribe(), 500);
will print:
gonna resolve
gonna resolve
gonna resolve
(...)
so yeah: gonna resolve will be printed in the cosole all the time, but nothing more - result passed to resolve will be ignored - just not logged.
From the other hand, if you remove that unsubscribtion (setTimeout(() => sub.unsubscribe(), 500);) this time you will see:
gonna resolve
foo
gonna resolve
gonna resolve
gonna resolve
(...)
There is one way that maybe will help you - defer - but it's not strictly related with your question.
import { defer } from 'rxjs';
defer(async () => {
const a = await Promise.resolve(1);
const b = a + await Promise.resolve(2);
return a + b + await Promise.resolve(3);
}).subscribe(x => console.log(x)) // logs 7

Javascript implement async to get result of long-running process into array

I have an array of files that I am adding data to which conceptually works like this:
let filearray = ['file1.txt', 'file2.txt', 'file3.txt'];
newarray = [];
for (let f of filearray) {
newstuff = 'newstuff';
newarray.push([f, newstuff]);
}
console.log(newarray)
// Returns expected array of arrays
However, what I need to do is make newstuff = slow_promise_function(f); that involves lots of processing. How do I get the value from that promise function into the array?
Ideally I'd like to use the new async feature in ES2017.
Update:
These answers are helping me understand the problems (and solutions):
https://stackoverflow.com/a/37576787/1061836
https://stackoverflow.com/a/43422983/1061836
You could use Promise.all which returns a single Promise that resolves when all of the promises have resolved:
let loadData = async () => {
let filearray = ['file1.txt', 'file2.txt', 'file3.txt'];
try {
let ops = filearray.map(f => slow_promise_function(f));
let newarray = await Promise.all(ops);
// TODO: use newarray
} catch (err) {
console.log(err);
}
}
loadData();
async/await is a nice way to accomplish this, as you suspected:
console.log('Starting...');
let files = ['file1.txt', 'file2.txt', 'file3.txt'];
Promise.all(files.map(async f => [f, await slow_promise_function(f)]))
.then(files => console.log('got results: ', files));
function slow_promise_function(file) {
return new Promise(res => setTimeout(_ => res(`processed ${file}`), 500));
}
Well that can simply be done with Promises more info on this link Promises.
const newStuffFunc = async () => {
try {
let newStuff = await slow_promise_function(f);
// new stuff is a promise. so you either do it with await again or with then/catch
let data = await newStuff;
} catch (e){
}
}
const slow_promise_function = (url) => {
return new Promise((resolve, reject) => {
// do something asynchronous which eventually calls either:
//
// resolve(someValue); // fulfilled
// or
// reject("failure reason"); // rejected
});
};
This link can show you more usability of async/promises into javascript.

Categories