async generator yielding promise results as they are resolved - async-await

Say I want to fetch 10 urls concurrently,
and process the responses as they are recieved
(which may be in a different order from the order
in which they appear in the original list).
Ignoring the possibility of rejections, one way to do this is simply to attach a "then" callback
to each promise, and then wait for them all to finish
using Promise.all().
const fetch_promises = [
fetch("https://cors-demo.glitch.me/allow-cors"),
fetch("/"),
fetch("."),
fetch(""),
fetch("https://enable-cors.org"),
fetch("https://html5rocks-cors.s3-website-us-east-1.amazonaws.com/index.html"),
fetch("https://api.github.com"),
fetch("https://api.flickr.com/services/rest/"),
];
const processing_promises = [];
for (const fetch_promise of fetch_promises) {
processing_promises.push(fetch_promise.then(response => {
// Process response. In this example, that means just
// print it.
console.log("got a response: ",response);
}));
}
await Promise.all(processing_promises);
Switching to an example with clearer and more deterministic output:
const sleep = millis => new Promise(resolve=>setTimeout(resolve, millis));
const sleep_promises = [
sleep(3000).then(()=>"slept 3000"),
sleep(1000).then(()=>"slept 1000"),
sleep(5000).then(()=>"slept 5000"),
sleep(4000).then(()=>"slept 4000"),
sleep(2000).then(()=>"slept 2000"),
];
const processing_promises = [];
for (const sleep_promise of sleep_promises) {
processing_promises.push(sleep_promise.then(result => {
console.log("promise resolved: ",result);
}));
}
await Promise.all(processing_promises);
The output is as expected:
15:54:16.331 promise resolved: slept 1000
15:54:17.331 promise resolved: slept 2000
15:54:18.331 promise resolved: slept 3000
15:54:19.332 promise resolved: slept 4000
15:54:20.331 promise resolved: slept 5000
My question is this: suppose I want to, or need to,
express the processing described above as an "async for..of" loop, instead of "then" callbacks;
so the promises results need to come out in the form of an async
iterable. How would I convert the array of promises
to such an async iterable? What I'm asking for is an async
generator function AwaitAsTheyCome(), taking as input a list of promises,
which yields the results one by one as the promises resolve.
I'd then call the function, and do the processing, as follows:
for await (const result of AwaitAsTheyCome(sleep_promises)) {
console.log("promise resolved: ",result);
}
It should give the same output (with the same timing) as above.
The following attempted solution obviously doesn't work, but it may give an idea of about how simple and short I expect this to be:
async function* AwaitAsTheyCome(promises) {
for (const promise of promises) {
promise.then(response => {
yield response; // WRONG
// I want to yield it from AwaitAsTheyCome,
// not from the current arrow function!
});
}
}
The following solution does work, but it's more code than I expected to have to write for this.
async function* AwaitAsTheyCome(promises) {
// Make a list of notifier promises and
// functions that resolve those promises,
// one for each of the original promises.
const notifier_promises = [];
const notifier_resolves = [];
for (const promise of promises) {
notifier_promises.push(
new Promise(resolve=>notifier_resolves.push(resolve)));
}
const responses = [];
for (const promise of promises) {
promise.then(response => {
responses.push(response);
// send one notification (i.e. resolve the next notifier promise)
notifier_resolves.shift()();
});
}
for (const promise of promises) {
// wait for one notification
// (i.e. wait for the next notifier promise to be resolved).
await notifier_promises.shift();
// yield the corresponding response
yield responses.shift();
}
}
// Example/test usage
const sleep = millis => new Promise(resolve=>setTimeout(resolve, millis));
const sleep_promises = [
sleep(3000).then(()=>"slept 3000"),
sleep(1000).then(()=>"slept 1000"),
sleep(5000).then(()=>"slept 5000"),
sleep(4000).then(()=>"slept 4000"),
sleep(2000).then(()=>"slept 2000"),
];
for await (const result of AwaitAsTheyCome(sleep_promises)) {
console.log("promise resolved: ",result);
}
Is there a simpler way to implement the async generator function AwaitAsTheyCome?
(I tried making a stacksnippet out of the above code, but it didn't work-- I suspect this is because the snippets system doesn't understand the new async generator and/or for await..of syntax)

You can simplify the code a bit by
using only a single loop over the input array (although that may be confusing)
not using a responses array but simply fulfilling the promises
not using .shift() on the promises array but simply looping it
async function* raceAll(input) {
const promises = [];
const resolvers = [];
for (const p of input) {
promises.push(new Promise(resolve=> {
resolvers.push(resolve);
}));
p.then(result => {
resolvers.shift()(result);
});
}
for (const promise of promises) {
yield promise;
}
}
If you don't like the amount of code required, I would recommend to factor out the queue this implements in a separate module. With e.g. this implementation, the code can become as simple as
function raceAll(promises) {
const queue = new AsyncBlockingQueue();
for (const p of promises) {
p.then(result => {
queue.enqueue(result);
});
}
return queue[Symbol.asyncIterator]();
}
However, both of these implementation miss a crucial issue: error handling. If any of these promises rejects, you'll get an unhandled rejection error which may crash your process. To actually get the async iterator to reject the next promise, so that a try/catch around a for await…of loop may handle it, you'd need to do something like
async function* raceAll(input) {
const promises = [];
const resolvers = [];
for (const p of input) {
promises.push(new Promise(resolve => {
resolvers.push(resolve);
}));
p.finally(() => {
resolvers.shift()(p);
});
// works equivalent to:
// p.then(result => {
// resolvers.shift()(result);
// }, error => {
// resolvers.shift()(Promise.reject(error));
// });
}
for (const promise of promises) {
yield promise;
}
}
Resolving the promise with a rejected promise does the trick so that we still only need one queue of resolver functions, not one containing both resolve and reject functions.

This does the same thing where whatever code you would put in your loop handling the yield results goes in the onfulfilled callback. The Promise.all() waits for all promises to finish like your loop.
const sleep = millis => new Promise(resolve=>setTimeout(resolve, millis));
const sleep_promises = [
sleep(3000).then(()=>"slept 3000"),
sleep(1000).then(()=>"slept 1000"),
sleep(5000).then(()=>"slept 5000"),
sleep(4000).then(()=>"slept 4000"),
sleep(2000).then(()=>"slept 2000"),
];
const onfulfilled = result => console.log("promise resolved: ",result);
sleep_promises.forEach( p => p.then(onfulfilled) )
await Promise.all(sleep_promises)

Related

RxJs channing, setting and reading external values

I'm new in rxjs world and I have to rewrite some code. So, I draft my ideas.
I have a request, which could fail and return an observable. I simulate that with the ob-variable and two map operations. Then, I try to catch an error. I need the result in my local variable selected and raise an event on isChanged. I call my function now via subscription. I don't need a result.
My question: Is one big pipe enough and can I use following approach for the work with my local variables?
import { of, map, Observable, tap, Subject, throwError, EMPTY } from 'rxjs';
import { catchError } from 'rxjs/operators';
let selected = 0;
const isChanged = new Subject<number>();
function myfunc(): Observable<boolean> {
const ob = of(1,3,4,5,7);
return ob.pipe(
// simulates a http request
map(v => v*2),
// simulates a rare error condition
map(v => {
// if (v === 8) { throw `four`; }
if (v === 10) { throw `ten`; }
return v;
}),
// play with different failure situations
catchError((e) => {
if (e === `four`) {
return of(4);
}
if (e === `ten`) {
return EMPTY;
}
console.warn(e);
return throwError(e);
}
),
// I need the result in a local variable
// I need a information about success
// I need the result not really
map((res) => {
selected = res;
isChanged.next(res);
return true;
})
);
}
console.log(`a: selected is ${selected}`);
isChanged.subscribe(v =>
console.log(`b: isChanged received: ${v}, selected is ${selected}`));
console.log(`c: selected is ${selected}`);
// I have to call the function
myfunc().subscribe((b) => {
console.log(`d: selected is ${selected}`);
});
I create the world in Stackblitz too:
https://stackblitz.com/edit/rxjs-6fgggh?devtoolsheight=66&file=index.ts
I see results like expected. But I'm not sure if all ideas are the right way to solve all problems.
Thanks for you thought.

GraphQL response array getting empty out of nowhere right before return(??)

So I'm trying to build a resolver for graphQL which is supposed to return an array of objects. The values for these objects come from a series of TypeORM selecting operations. And when I tried asking for a response in the graphql playground I only got empty arrays, so I started debugging the resolver using console.logs, but the thing is, inside the forEach loops I use the code seems to have the desired result: an array with objects in it. But when I log the same array right before returning it is empty:
#Query(() => [response])
async getTeacherFromSubjectName(
#Arg("subjectName") subjectName: string,
): Promise<response[] | undefined> {
const subject = await Subject.findOne({name: subjectName});
const subjectId = subject?.id;
let responseArray: response[] = [];
const qb = await getConnection()
.createQueryBuilder()
.select("teacher")
.from(Teacher, "teacher")
.where(`teacher.subjectId = ${subjectId}`)
.getMany()
qb.forEach(async (teacher) => {
const qb = await getConnection()
.createQueryBuilder()
.select("lectureTime")
.from(LectureTime, "lectureTime")
.where(`lectureTime.teacherId = ${teacher.id}`)
.getMany()
responseArray.push( {
teacher: teacher.name,
lectures: qb,
} );
console.log(responseArray) // [{ teacher: 'Dirceu', lectures: [ [LectureTime] ] }, { teacher:
'Patrícia', lectures: [ [LectureTime], [LectureTime] ] } ]
})
console.log(responseArray) // []
return responseArray;
}
What I get on the console is the following:
link to image
I actually have no idea of what is going on here, you can see in the image that the order of the logs is inverted (log right before return is circled in blue).
I am certain that it is a silly problem and if you guys could point it out for me I would be very thankful.
As xadm said on the comments, you are not awaiting for the promises you are creating inside forEach. In that case you need to map to the promises, so you'll have an array of the Promises and them await them all.
// Changed to map so you get the return values
const promises = qb.map(async (teacher) => {
const qb = await getConnection()
.createQueryBuilder()
.select("lectureTime")
.from(LectureTime, "lectureTime")
.where(`lectureTime.teacherId = ${teacher.id}`)
.getMany()
responseArray.push( {
teacher: teacher.name,
lectures: qb,
} );
console.log(responseArray);
});
// Await for all the promises to be completed, regardless of order.
await Promise.all(promises);

redux observable map not invoked

I have this code, and failing to understand why I am not getting inside the map function (where I have the comment "I AM NEVER GETTING TO THIS PART OF THE CODE"):
export const fiveCPMonitoringLoadEpic = (action$, store) =>
action$
.ofType(
FIVE_CP_MONITORING_ACTION_TYPES.LOAD_FIVE_CP_MONITORING_DATA_STARTED
)
.debounceTime(250)
.switchMap(action => {
const params = action.params;
const siteId = { params };
// getting site's EDC accounts (observable):
const siteEdcAccount$ = getSiteEDCAccountsObservable(params);
const result$ = siteEdcAccount$.map(edcResponse => {
// getting here - all good so far.
const edcAccount = edcResponse[0];
// creating another observable (from promise - nothing special)
const fiveCPMonitoringEvent$ = getFiveCPAndTransmissionEventsObservable(
{
...params,
edcAccountId: edcAccount.utilityAccountNumber
}
);
fiveCPMonitoringEvent$.subscribe(x => {
// this is working... I am getting to this part of the code
// --------------------------------------------------------
console.log(x);
console.log('I am getting this printed out as expected');
});
return fiveCPMonitoringEvent$.map(events => {
// I NEVER GET TO THIS PART!!!!!
// -----------------------------
console.log('----- forecast-----');
// according to response - request the prediction (from the event start time if ACTIVE event exists, or from current time if no active event)
const activeEvent = DrEventUtils.getActiveEvent(events);
if (activeEvent) {
// get event start time
const startTime = activeEvent.startTime;
// return getPredictionMeasurementsObservable({...params, startTime}
const predictions = getPredictionMock(startTime - 300);
return Observable.of(predictions).delay(Math.random() * 2000);
} else {
// return getPredictionMeasurementsObservable({...params}
const predictions = getPredictionMock(
DateUtils.getLocalDateInUtcSeconds(new Date().getTime())
);
return Observable.of(predictions).delay(Math.random() * 2000);
}
});
can someone please shed some light here?
why when using subscribe it is working, but when using map on the observable it is not?
isn't map suppose to be invoked every time the observable fires?
Thanks,
Jim.
Until you subscribe to your observable, it is cold and does not emit values. Once you subscribe to it, the map will be invoked. This is a feature of rxjs meant to avoid operations that make no change (= no cunsumer uses the values). There are numerous blog posts on the subject, search 'cold vs hot obserables' on google

RxJS Emitting Subscribe Twice

I have a RXJS function that will create an empty Observable, tap into the result and return that new observable. I want the observable to always run the tap so I noop subscribe (in the real case it might not ever be subscribed to).
function that() {
let obs = of({});
obs = obs.pipe(tap(() => console.log('here')))
obs.subscribe();
return obs;
}
const res = that();
res.subscribe(() => console.log('finished'))
If you run this code on StackBlitz, you will notice that here is fired twice. The output looks like this:
here
here
finished
I've tried several different approaches but I can't ever seem to get it to work where it doesn't emit twice.
You subscribe TWICE:
function that() {
let obs = of({});
obs = obs.pipe(tap(() => console.log('here')))
obs.subscribe(); // first subscription
return obs;
}
const res = that();
res.subscribe(() => console.log('finished')) // second subscription
This is the same observable you subscribe to, once in the function, then on the returned value.
Just don't subscribe in the function
function that() {
let obs = of({});
obs = obs.pipe(tap(() => console.log('here')))
return obs;
}
const res = that();
res.subscribe(() => console.log('finished')) // subscribe from here only
See the updated StackBlitz.
Is it just a case of only tapping only the inner subscription?
function that() {
let obs = of({});
obs.pipe(tap(() => console.log('here'))).subscribe();
return obs;
}
const res = that();
res.subscribe(() => console.log('finished'))

Operator that skips the next emission from the source whenever another Observable emits

I have a use case where I need an Observable to skip its next emission whenever another notifier Observable emits.
source: |---X---X---X---X---X---X---X---X---X---X--|>
notifier: |-------------X---------X----------X-------|>
result: |---X---X---X-------X---X-------X-------X--|>
Basically, I want an operator called skipNextWhen that takes in the notifier observable and skips the next emission from the source.
I tried using an implementation that uses the pausable operator (re-implemented using switchMap), but couldn't get it to work.
pausable.ts
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/observable/never';
import 'rxjs/add/operator/startWith';
declare module 'rxjs/Observable' {
interface Observable<T> {
pausable: typeof pausable;
}
}
function pausable<T>(notifier: Observable<boolean>): Observable<T> {
return notifier.startWith(false).switchMap((paused) => {
if (paused) {
return Observable.never();
} else {
const source = new Subject();
this.subscribe(source);
return source;
}
});
}
Observable.prototype.pausable = pausable;
skipNextWhen.ts
import { Observable } from 'rxjs/Observable';
import './pausable';
declare module 'rxjs/Observable' {
interface Observable<T> {
skipNextWhen: typeof skipNextWhen;
}
}
function skipNextWhen<T, R>(other: Observable<T>): Observable<R> {
const notifier = Observable.merge(this.map(() => false),
other.map(() => true));
return this.pausable(notifier);
}
Observable.prototype.skipNextWhen = skipNextWhen;
Is there a more suitable operator that I should consider using instead? The behavior I'm seeing with my current implementation is that the result Observable emits once, and then never again - even if the notifier Observable never emits.
I can think of two solutions to this:
Using .filter(), .do() and a few side-effects.
This is mayne easier to understand solution even though it's not that "Rx" way:
function skipNextWhen(other) {
let skipNext = false;
return this.merge(other.do(() => skipNext = true).filter(() => false))
.filter(val => {
const doSkip = skipNext;
skipNext = false;
return !doSkip;
});
}
I'm using merge() just to update skipNext, other's value is always ignored.
Using .scan():
This solution is without any state variables and side-effects.
function skipNextWhen(other) {
const SKIP = 'skip';
return this.merge(other.mapTo(SKIP))
.scan((acc, val) => {
if (acc === SKIP) {
return null;
} else if (val === SKIP) {
return SKIP;
} else {
return val;
}
}, [])
.filter(val => Boolean(val) && val !== SKIP);
}
Basically, when SKIP arrives I return it right away because it's going to be passed again in acc parameter by the scan() operator and later ignored by filter().
If I receive a normal value but the previous value was SKIP I ignore it and return just null which is later filter away.
Both solutions give the same result:
Observable.prototype.skipNextWhen = skipNextWhen;
const source = Observable.range(1, 10)
.concatMap(val => Observable.of(val).delay(100));
source
.skipNextWhen(Observable.interval(350))
.subscribe(console.log);
This prints the following:
1
2
3
5
6
8
9
10
Just be aware that you're not in fact creating new operator. You just have a shortcut for an operator chain. This for example doesn't let you unsubscribe from other when the source completes.
I've started a (very) small library of some rxjs utils I've wanted. It happens to have a function to do exactly what you ask: skipAfter. From the docs:
source: -1-----2-----3-----4-----5-|
skip$: ----0----------0-0----------
result: -1-----------3-----------5-|
The library is here: https://github.com/simontonsoftware/s-rxjs-utils

Resources