Chaining Observables in RxJS - javascript

I'm learning RxJS and Angular 2. Let's say I have a promise chain with multiple async function calls which depend on the previous one's result which looks like:
var promiseChain = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000);
}).then((result) => {
console.log(result);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(result + 2);
}, 1000);
});
}).then((result) => {
console.log(result);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(result + 3);
}, 1000);
});
});
promiseChain.then((finalResult) => {
console.log(finalResult);
});
My attempts at doing the same solely using RxJS without the use of promises produced the following:
var observableChain = Observable.create((observer) => {
setTimeout(() => {
observer.next(1);
observer.complete();
}, 1000);
}).flatMap((result) => {
console.log(result);
return Observable.create((observer) => {
setTimeout(() => {
observer.next(result + 2);
observer.complete()
}, 1000);
});
}).flatMap((result) => {
console.log(result);
return Observable.create((observer) => {
setTimeout(() => {
observer.next(result + 3);
observer.complete()
}, 1000);
});
});
observableChain.subscribe((finalResult) => {
console.log(finalResult);
});
It yields the same output as the promise chain. My questions are
Am I doing this right? Are there any RxJS related improvements that I can make to the above code
How do I get this observable chain to execute repeatedly? i.e. Adding another subscription at the end just produces an additional 6 though I expect it to print 1, 3 and 6.
observableChain.subscribe((finalResult) => {
console.log(finalResult);
});
observableChain.subscribe((finalResult) => {
console.log(finalResult);
});
1
3
6
6

About promise composition vs. Rxjs, as this is a frequently asked question, you can refer to a number of previously asked questions on SO, among which :
How to do the chain sequence in rxjs
RxJS Promise Composition (passing data)
RxJS sequence equvalent to promise.then()?
Basically, flatMap is the equivalent of Promise.then.
For your second question, do you want to replay values already emitted, or do you want to process new values as they arrive? In the first case, check the publishReplay operator. In the second case, standard subscription is enough. However you might need to be aware of the cold. vs. hot dichotomy depending on your source (cf. Hot and Cold observables : are there 'hot' and 'cold' operators? for an illustrated explanation of the concept)

Example for clarification:
Top of pipe can emit n values (this answers "How do I get this observable chain to execute repeatedly"), but subsequent chained streams emit one value (hence mimicing promises).
// Emit three values into the top of this pipe
const topOfPipe = of<string>('chaining', 'some', 'observables');
// If any of the chained observables emit more than 1 value
// then don't use this unless you understand what is going to happen.
const firstObservablePipe = of(1);
const secondObservablePipe = of(2);
const thirdObservablePipe = of(3);
const fourthObservablePipe = of(4);
const addToPreviousStream = (previous) => map(current => previous + current);
const first = (one) => firstObservablePipe.pipe(addToPreviousStream(one));
const second = (two) => secondObservablePipe.pipe(addToPreviousStream(two));
const third = (three) => thirdObservablePipe.pipe(addToPreviousStream(three));
const fourth = (four) => fourthObservablePipe.pipe(addToPreviousStream(four));
topOfPipe.pipe(
mergeMap(first),
mergeMap(second),
mergeMap(third),
mergeMap(fourth),
).subscribe(console.log);
// Output: chaining1234 some1234 observables1234
You could also use concatMap or switchMap. They all have subtle differences. See rxjs docs to understand.
mergeMap:
https://www.learnrxjs.io/learn-rxjs/operators/transformation/mergemap
concatMap:
https://www.learnrxjs.io/learn-rxjs/operators/transformation/concatmap
switchMap:
https://www.learnrxjs.io/learn-rxjs/operators/transformation/switchmap

Related

Handle multiple requests at the same time and update state when one of them is resolved

I have following scenario:
Multiple http requests will be executed:
const sourceOne = http.get(obj1.product_id);
const sourceTwo = http.get(obj2.product_id);
const sourceThree = http.get(obj3.product_id);
const sourceFour = http.get(obj4.product_id);
can i somehow using Promises or Observables to:
Execute all of the requests at the same time
Update state when one of them is resolved, for example:
sourceThree, finish first - update state on the client
sourceFour, complete after sourceThree - update state on the client
All of the possible solutions that i can found are always wait for all of the requests to be completed before updating the state, any ideas?
You could make use of merge to turn multiple observables into a single observable.
merge(sourceOne, sourceTwo, sourceThree, sourceFour).subscribe((data) => updateState(data))
Here is a link to a stackblitz example: https://stackblitz.com/edit/rxjs-cg31qz?devToolsHeight=33&file=index.ts
I think what you are looking for Promise.race, it will populate the first promise has been resolved.
The Promise.race() method returns a promise that fulfills or rejects as soon as one of the promises in an iterable fulfills or rejects, with the value or reason from that promise.
const arr [obj1.product_id, obj2.product_id, obj3.product_id, obj4.product_id]
const result = Promise.race(arr.map(i => http.get(i))
The answer to your question is Promise.race() which fires, everything in then() callback function, after one of the passed promises has been resolved.
const prom1 = new Promise(r => {
setTimeout(() => {
r('prom1')
}, 1200)
})
const prom2 = new Promise(r => {
setTimeout(() => {
r('prom2')
}, 600)
})
const prom3 = new Promise(r => {
setTimeout(() => {
r('prom3')
}, 1000)
})
const prom4 = new Promise(r => {
setTimeout(() => {
r('prom4')
}, 700)
})
Promise.race([prom1, prom2, prom3, prom4]).then(r => {
console.log(r)
})
If you have RxJS installed, observables can do that.
All at once
forkJoin([sourceOne, sourceTwo, sourceThree, sourceFour])
.subscribe(([one, two, three, four]) => {});
Update client when one of them resolves :
merge(sourceOne, sourceTwo, sourceThree, sourceFour)
.subscribe(() => updateUi());

Possible implementation of Promise.any on JS

In Promise.race the promise returns as soon that the primary promise returns. In Promise.all returns when all promises resolves, but lasts one problem. If any of all promises rejects all others will be rejected.
Instead of it, exists a proposal for a Promise.any, the returns every promise alone, independent of each other, short-circuiting on a rejection.
const logAfterWait = (seconds) => new Promise((resolve, reject) => {
return setTimeout(() => resolve(console.log(`${time} time passed`)), seconds)
})
const watingList = [
logAfterWait(convertToSeconds(10)),
logAfterWait(convertToSeconds(30)),
logAfterWait(convertToSeconds(5))
]
const logReading = async (fn) => {
console.log(`${time}: reading file`)
await fn()
}
const readFiles = (files) => Promise.all(watingList.map(logReading))
.catch((error) => new Error(error))
The problem here is the block of event loop on the maping cause block on event loop on Promise.all, returning every results on the same time, differ from the expected result, that is, 5, 10, 30 seconds.
Can I avoid this situation on waitingList.map?
You can leverage the fact that Promise.race forms a monoid by creating a Promise that never settles:
const empty = x => new Promise((res, rej) => x); // never settling promise
const ps = [
Promise.reject(1).catch(empty),
Promise.resolve(2).catch(empty),
Promise.resolve(3).catch(empty)];
Promise.race(ps)
.then(console.log); // 2
You need to attach a catch handler to each Promise in the array though. You can probably create a utility function that does this for you.
You could think of something like this:
// a solution might just be not using async/await
const any = (promises) => new Promise((resolve, reject) => {
let errors = [];
let resolved;
const onFulfill = (value) => {
// skip if already resolved
if (resolved) { return; }
resolved = true;
// resolve with the first available value
resolve(value);
};
const onError = (error) => {
// skip if already resolved
if (resolved) { return; }
// collect error
errors = errors.concat(error);
// reject promise combinator if all promises are failed
if (errors.length === promises.length) {
reject(errors);
}
};
return promises.forEach((promise) => promise.then(
onFulfill,
onError,
));
});
const sleep = (ms) => new Promise(r => setTimeout(() => r(ms), ms));
const err = (ms) => sleep(ms).then(() => Promise.reject(ms));
// it would log 2000, since it is the first to resolve
any([sleep(3000), err(100), sleep(2000)]).then(console.info)
// it would an array of 2 failures
any([err(50), err(60)]).catch(console.error)
the block of IO
Note that there isn't any block of IO in javascript, the thread is just free to tackle any other task while waiting for the promises to be resolved.
Consequently, I came to a conclusion. We create a resolver that is an Either monad(not a pure implementation of the Either monad) that returns [err, response] over a map function.
The catch blocks are necessary to avoid the Unhandled Promise Rejection Warning.
const time = () => `${new Date().getHours()}:${new Date().getMinutes()}:${new Date().getSeconds()}`;
const sleep = (ms, pNumber) => new Promise((resolve, reject) => {
return pNumber < 3
? setTimeout(() => resolve(console.log(`${time()} time passed`)), ms)
: reject(null)
}).catch(null)
Promise.prototype.resolver = async (promise) => {
this._result = await Promise.all([promise])[0];
return this._result == null
? ["The time flies", promise]
: [null, promise]
}
const watingList = [
Promise.resolver(sleep(0, 0).catch(console.error)),
Promise.resolver(sleep(3000, 1).catch(console.error)),
Promise.resolver(sleep(5000, 2).catch(console.error)),
Promise.resolver(sleep(5000, 3).catch(console.error))
]
const logReading = (list) => {
return list.map(p => p.then(console.log(`${time()}: reading file`))
.catch(console.log))
}
((read) => logReading(read))(watingList)
PS: time function differs from the expected because of the evaluate time.
Resources can be found here:
1 - https://frontendmasters.com/courses/hardcore-js-v2/either-monad/

Rxjs subscription queue

I have a firebase subscription in my angular app which fires multiple times.
How can ich achieve that the tasks are processed as a queue so that I can run each task synchronously once?
this.tasks.subscribe(async tasks => {
for (const x of tasks)
await dolongtask(x); // has to be sync
await removetask(x);
});
The problem is that the subribe event fires when the longtask is still processing.
IMHO, I would try and leverage the power of rxjs since we're using it here already anyway and avoid implementing a custom queuing concept as suggested by another answer (though you certainly can do that).
If we simplify the given case a bit, we just have some observable and want to perform a long-running procedure for each emission – in sequence. rxjs allows doing this by means of the concatMap operator essentially out of the box:
$data.pipe(concatMap(item => processItem(item))).subscribe();
This only assumes that processItem returns an observable. Since you used await, I assume your function(s) currently return Promises. These can be trivially converted into observables using from.
The only detail left to look at from the OP is that the observable actually emits an array of items and we want to perform the operation on each item of each emission. To do that, we just flatten the observable using mergeMap.
Let's put it all together. Note that if you take away preparing some stub data and logging, the actual implementation of this is only two lines of code (using mergeMap + concatMap).
const { from, interval } = rxjs;
const { mergeMap, concatMap, take, bufferCount, tap } = rxjs.operators;
// Stub for the long-running operation
function processTask(task) {
console.log("Processing task: ", task);
return new Promise(resolve => {
setTimeout(() => {
console.log("Finished task: ", task);
resolve(task);
}, 500 * Math.random() + 300);
});
}
// Turn processTask into a function returning an observable
const processTask$ = item => from(processTask(item));
// Some stubbed data stream
const tasks$ = interval(250).pipe(
take(9),
bufferCount(3),
);
tasks$.pipe(
tap(task => console.log("Received task: ", task)),
// Flatten the tasks array since we want to work in sequence anyway
mergeMap(tasks => tasks),
// Process each task, but do so consecutively
concatMap(task => processTask$(task)),
).subscribe(null, null, () => console.log("Done"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.3.2/rxjs.umd.js"></script>
I am making a couple of assumptions from the code you gave,
other applications add tasks to the firebase db (asynchronously), and this code is implementing the task processor.
your firebase query returns all unprocessed tasks (in a collection) and it emits the full list every time a new task is added.
the query will drop a task only after removeTask() has been run
If this is so, you need a deduping mechanism before the processor.
For the purpose of illustration, I've simulated the firebase query with a subject (renamed it to tasksQuery$) and a sequence of firebase events are simulated at the bottom of the script.
I hope it's not too confusing!
console.clear()
const { mergeMap, filter } = rxjs.operators;
// Simulate tasks query
const tasksQuery$ = new rxjs.Subject();
// Simulate dolongtask and removetask (assume both return promises that can be awaited)
const dolongtask = (task) => {
console.log( `Processing: ${task.id}`);
return new Promise(resolve => {
setTimeout(() => {
console.log( `Processed: ${task.id}`);
resolve('done')
}, 1000);
});
}
const removeTask = (task) => {
console.log( `Removing: ${task.id}`);
return new Promise(resolve => {
setTimeout(() => {
console.log( `Removed: ${task.id}`);
resolve('done')
}, 200);
});
}
// Set up queue (this block could be a class in Typescript)
let tasks = [];
const queue$ = new rxjs.Subject();
const addToQueue = (task) => {
tasks = [...tasks, task];
queue$.next(task);
}
const removeFromQueue = () => tasks = tasks.slice(1);
const queueContains = (task) => tasks.map(t => t.id).includes(task.id)
// Dedupe and enqueue
tasksQuery$.pipe(
mergeMap(tasks => tasks), // flatten the incoming task array
filter(task => task && !queueContains(task)) // check not in queue
).subscribe(task => addToQueue(task) );
//Process the queue
queue$.subscribe(async task => {
await dolongtask(task);
await removeTask(task); // Assume this sends 'delete' to firebase
removeFromQueue();
});
// Run simulation
tasksQuery$.next([{id:1},{id:2}]);
// Add after delay to show repeated items in firebase
setTimeout(() => {
tasksQuery$.next([{id:1},{id:2},{id:3}]);
}, 500);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.3.2/rxjs.umd.js"></script>
Leaving aside your title 'Rxjs subscription queue', you can actually fix your async/await code.
The problem is that async/await does not play nicely with for loops, see this question Using async/await with a forEach loop.
For example, you can replace the for loop as per #Bergi's answer,
with Promise.all()
console.clear();
const { interval } = rxjs;
const { take, bufferCount } = rxjs.operators;
function processTask(task) {
console.log(`Processing task ${task}`);
return new Promise(resolve => {
setTimeout(() => {
resolve(task);
}, 500 * Math.random() + 300);
});
}
function removeTask(task) {
console.log(`Removing task ${task}`);
return new Promise(resolve => {
setTimeout(() => {
resolve(task);
}, 50);
});
}
const tasks$ = interval(250).pipe(
take(10),
bufferCount(3),
);
tasks$.subscribe(async tasks => {
await Promise.all(
tasks.map(async task => {
await processTask(task); // has to be sync
await removeTask(task);
console.log(`Finished task ${task}`);
})
);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.3.2/rxjs.umd.js"></script>
Better yet, you can shape the query to avoid using a for loop,
with mergeMap()
console.clear();
const { interval } = rxjs;
const { mergeMap, take, bufferCount } = rxjs.operators;
function processTask(task) {
console.log(`Processing task ${task}`);
return new Promise(resolve => {
setTimeout(() => {
resolve(task);
}, 500 * Math.random() + 300);
});
}
function removeTask(task) {
console.log(`Removing task ${task}`);
return new Promise(resolve => {
setTimeout(() => {
resolve(task);
}, 50);
});
}
const tasks$ = interval(250).pipe(
take(10),
bufferCount(3),
);
tasks$
.pipe(mergeMap(tasks => tasks))
.subscribe(
async task => {
await processTask(task); // has to be sync
await removeTask(task);
console.log(`Finished task ${task}`);
}
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.3.2/rxjs.umd.js"></script>

JavaScript Promises: conventional way to pass arguments

How do i pass additional arguments to next "step" of promise?
new Promise((resolve, reject) => {
const a = // do stuff and return a string
return Promise.all([
// execute another promise,
// execute yet another promise
])
})
.then(([resultFromPromise_1, resultFromPromise_2]) => {
// how do i pass `const a` here?
})
I can add something like new Promise(resolve => resolve(a)) into Promise.all array, but this looks ugly. Is there better way to pass data in such cases?
I can add something like new Promise(resolve => resolve(a)) into Promise.all array, but this looks ugly. Is there better way to pass data in such cases?
Yes: Use then. If you already have a promise, using new Promise is never needed. then creates a promise, which waits for the resolution of the one you called it on, and then gets resolved with what you return from the then callback or gets rejected if you throw an exception. One of the keys of promises is how using then (and catch) transforms things at each link in the chain.
In that specific case, you'd use then on the original promise and use its callback to transform the result using a (although if you want to wait until they're all done, you can do that too; covered later).
Side note: The new Promise line at the beginning of the code of your question shouldn't be there, you don't return a promise out of the promise executor (the callback you pass to new Promise).
Example:
const a = "some string";
Promise.all([
getPromise("one").then(result => result + " - " + a), // ***
getPromise("two")
])
.then(results => {
console.log(results);
});
function getPromise(str) {
// (Could use Promise.resolve here; emphasizing asynchronousness)
return new Promise(resolve => {
setTimeout(() => {
resolve(str);
}, 250);
});
}
Alternately, if you really only want to use a when all of the promises you're passing to Promise.all have resolved, you can do that, too:
const a = "some string";
Promise.all([
getPromise("one"),
getPromise("two")
])
.then(([result1, result2]) => {
return [result1 + " - " + a, result2]; // ***
})
.then(results => {
console.log(results);
});
function getPromise(str) {
// (Could use Promise.resolve here; emphasizing asynchronousness)
return new Promise(resolve => {
setTimeout(() => {
resolve(str);
}, 250);
});
}
First off, your first promise has an error, you're not resolving it. You should do something like this:
new Promise((resolve, reject) => {
const a = 1;
resolve(Promise.all([
...
]))
})
And as for your question, instead of new Promise(resolve => resolve(a)) you can just pass a directly to the all array. ie:
new Promise((resolve, reject) => {
const a = 1;
resolve(Promise.all([
Promise.resolve("a"),
Promise.resolve("b"),
a,
]))
})
.then(([resultFromPromise_1, resultFromPromise_2, a]) => {
console.log(a);
})

Get values from an array of promises

I've just started learning this amazing stuff. I can't figure out how to get values from an array of promises. Here's where am at:
const one = new Promise(resolve => {
setTimeout(() => {
resolve(1);
}, 1000);
})
const two = new Promise(resolve => {
setTimeout(() => {
resolve(2);
}, 2000);
})
const observable = Rx.Observable.from([one, two]);
observable.subscribe(v => console.log(v));
I get in console:
Promise { <pending> }
Promise { <pending> }
I'd like to get:
Result as an array of values [1,2]
Result as individual values in order of promise resolution 1,2
So, basically I want to emulate:
Promise.all([one, two])
Promise.resolve(1), Promise.resolve(2)
Static method Observable.from() emits each item in the array so what you have right now will just emit two Promise objects:
You're dealing with so called Higher-order Observables (aka Observables emitting Observables). This is in RxJS 5 easily solvable with concatAll or mergeAll depending on whether you care about the order they are specified or they can be collected as the resolve.
RxJS 5 treats Observables, Promises, iterators, array (and array like objects) the same way. This means we use your Promises just like they were Observables.
I'm using mergeAll here to show that the second Promise finished first even though they're defined in the opposite order [one, two].
const one = new Promise(resolve => {
setTimeout(() => {
resolve(1);
}, 1000);
})
const two = new Promise(resolve => {
setTimeout(() => {
resolve(2);
}, 500);
})
// Result as individual values in order of promise resolution 2,1
Rx.Observable.from([one, two])
.mergeAll()
.subscribe(v => console.log('mergeAll: ' + v));
// Result as an array of values [2,1]
Rx.Observable.from([one, two])
.concatAll()
.toArray()
.subscribe(v => console.log(v));
See live demo: https://jsbin.com/tigidon/4/edit?js,console
This prints to console:
mergeAll: 2
mergeAll: 1
[2, 1]

Categories