How to use Promises in RactiveJS? - promise

Although there is an example in Ractive docs, I am a starter with promises and I don't understand the mentioned example:
var Promise = Ractive.Promise;
var p = new Promise( function ( fulfil, reject ) {
doSomethingAsync( function ( error, result ) {
if ( error ) {
return reject( error );
}
fulfil( result );
});
});
How can I use Ractive's implementation to run some functions asynchronously?
Edit: An use case example is when I have, in the same function, synchronous and asynchronous operations and I need to return a Promise when all of these operations where processed.

This is more a question about promises than about Ractive, so this MDN article would be worth a read, though it's a bit heavy.
Basically though, if you want to wait until several operations have finished, use Promise.all:
var ready = Promise.all([
getJSON( '/data.json' ), // async (returns a promise)
domReady(), // async
reticulateSplines() // sync
]);
getJSON and domReady will be executed simultaneously. So will reticulateSplines, not that it matters since it's synchronous. The value of ready is now a promise that will fulfil with an array containing the results of those three operations:
ready.then( function ( values ) {
var data = values[0];
var document = values[1];
var splines = values[2];
// some code happens
}).catch( function ( err ) {
// if something went wrong with any of those three
// functions (e.g. couldn't find data.json), we'll
// end up here
});
If you're transpiling your code with something like babel, you can also use destructuring:
ready.then( ([ data, document, splines ]) => {
// some code happens
}).catch( err => {
// handle error
});
Another useful function if you're dealing with maybe-sync-maybe-async stuff (though it really is better to avoid that sort of thing) is Promise.resolve:
function crazyTown () {
if ( Math.random() < 0.5 ) {
return 'sync!';
} else {
return new Promise( function ( fulfil, reject ) {
setTimeout( function () {
fulfil( 'async!' );
}, 500 );
});
}
}
Promise.resolve( crazyTown() ).then( function ( type ) {
console.log( type );
});
If your browser supports promises natively, Ractive will use those (i.e. Ractive.Promise === window.Promise) - if not, it uses its own spec-compliant implementation.

Related

Delay batch of observables with RxJS

I perform http requests to my db and have noticed that if I send all the requests at once, some of them will get a timeout errors. I'd like to add a delay between calls so the server doesn't get overloaded. I'm trying to find the RxJS solution to this problem and don't want to add a setTimeout.
Here is what I currently do:
let observables = [];
for(let int = 0; int < 10000; int++){
observables.push(new Observable((observer) => {
db.add(doc[int], (err, result)=>{
observer.next();
observer.complete();
})
}))
}
forkJoin(observables).subscribe(
data => {
},
error => {
console.log(error);
},
() => {
db.close();
}
);
You can indeed achieve this with Rxjs quite nicely. You'll need higher order observables, which means you'll emit an observable into an observable, and the higher order observable will flatten this out for you.
The nice thing about this approach is that you can easily run X requests in // without having to manage the pool of requests yourself.
Here's the working code:
import { Observable, Subject } from "rxjs";
import { mergeAll, take, tap } from "rxjs/operators";
// this is just a mock to demonstrate how it'd behave if the API was
// taking 2s to reply for a call
const mockDbAddHtppCall = (id, cb) =>
setTimeout(() => {
cb(null, `some result for call "${id}"`);
}, 2000);
// I have no idea what your response type looks like so I'm assigning
// any but of course you should have your own type instead of this
type YourRequestType = any;
const NUMBER_OF_ITEMS_TO_FETCH = 10;
const calls$$ = new Subject<Observable<YourRequestType>>();
calls$$
.pipe(
mergeAll(3),
take(NUMBER_OF_ITEMS_TO_FETCH),
tap({ complete: () => console.log(`All calls are done`) })
)
.subscribe(console.log);
for (let id = 0; id < NUMBER_OF_ITEMS_TO_FETCH; id++) {
calls$$.next(
new Observable(observer => {
console.log(`Starting a request for ID "${id}""`);
mockDbAddHtppCall(id, (err, result) => {
if (err) {
observer.error(err);
} else {
observer.next(result);
observer.complete();
}
});
})
);
}
And a live demo on Stackblitz: https://stackblitz.com/edit/rxjs-z1x5m9
Please open the console of your browser and note that the console log showing when a call is being triggered starts straight away for 3 of them, and then wait for 1 to finish before picking up another one.
Looks like you could use an initial timer to trigger the http calls. e.g.
timer(delayTime).pipe(combineLatest(()=>sendHttpRequest()));
This would only trigger the sendHttpRequest() method after the timer observable had completed.
So with your solution. You could do the following...
observables.push(
timer(delay + int).pipe(combineLatest(new Observable((observer) => {
db.add(doc[int], (err, result)=>{
observer.next();
observer.complete();
}))
}))
Where delay could probably start off at 0 and you could increase it using the int index of your loop by some margin.
Timer docs: https://www.learnrxjs.io/learn-rxjs/operators/creation/timer
Combine latest docs: https://www.learnrxjs.io/learn-rxjs/operators/combination/combinelatest
merge with concurrent value:
mergeAll and mergeMap both allow you to define the max number of subscribed observables. mergeAll(1)/mergeMap(LAMBDA, 1) is basically concatAll()/concatMap(LAMBDA).
merge is basically just the static mergeAll
Here's how you might use that:
let observables = [...Array(10000).keys()].map(intV =>
new Observable(observer => {
db.add(doc[intV], (err, result) => {
observer.next();
observer.complete();
});
})
);
const MAX_CONCURRENT_REQUESTS = 10;
merge(...observables, MAX_CONCURRENT_REQUESTS).subscribe({
next: data => {},
error: err => console.log(err),
complete: () => db.close()
});
Of note: This doesn't batch your calls, but it should solve the problem described and it may be a bit faster than batching as well.
mergeMap with concurrent value:
Perhaps a slightly more RxJS way using range and mergeMap
const MAX_CONCURRENT_REQUESTS = 10;
range(0, 10000).pipe(
mergeMap(intV =>
new Observable(observer => {
db.add(doc[intV], (err, result) => {
observer.next();
observer.complete();
});
}),
MAX_CONCURRENT_REQUESTS
)
).subscribe({
next: data => {},
error: err => console.log(err),
complete: () => db.close()
});

Keeping error information and the outer observable alive

To ensure an error doesn't complete the outer observable, a common rxjs effects pattern I've adopted is:
public saySomething$: Observable<Action> = createEffect(() => {
return this.actions.pipe(
ofType<AppActions.SaySomething>(AppActions.SAY_SOMETHING),
// Switch to the result of the inner observable.
switchMap((action) => {
// This service could fail.
return this.service.saySomething(action.payload).pipe(
// Return `null` to keep the outer observable alive!
catchError((error) => {
// What can I do with error here?
return of(null);
})
)
}),
// The result could be null because something could go wrong.
tap((result: Result | null) => {
if (result) {
// Do something with the result!
}
}),
// Update the store state.
map((result: Result | null) => {
if (result) {
return new AppActions.SaySomethingSuccess(result);
}
// It would be nice if I had access the **error** here.
return new AppActions.SaySomethingFail();
}));
});
Notice that I'm using catchError on the inner observable to keep the outer observable alive if the underlying network call fails (service.saySomething(action.payload)):
catchError((error) => {
// What can I do with error here?
return of(null);
})
The subsequent tap and map operators accommodate this in their signatures by allowing null, i.e. (result: Result | null). However, I lose the error information. Ultimately when the final map method returns new AppActions.SaySomethingFail(); I have lost any information about the error.
How can I keep the error information throughout the pipe rather than losing it at the point it's caught?
As suggested in comments you should use Type guard function
Unfortunately I can't run typescript in snippet so I commented types
const { of, throwError, operators: {
switchMap,
tap,
map,
catchError
}
} = rxjs;
const actions = of({payload: 'data'});
const service = {
saySomething: () => throwError(new Error('test'))
}
const AppActions = {
}
AppActions.SaySomethingSuccess = function () {
}
AppActions.SaySomethingFail = function() {
}
/* Type guard */
function isError(value/*: Result | Error*/)/* value is Error*/ {
return value instanceof Error;
}
const observable = actions.pipe(
switchMap((action) => {
return service.saySomething(action.payload).pipe(
catchError((error) => {
return of(error);
})
)
}),
tap((result/*: Result | Error*/) => {
if (isError(result)) {
console.log('tap error')
return;
}
console.log('tap result');
}),
map((result/*: Result | Error*/) => {
if (isError(result)) {
console.log('map error')
return new AppActions.SaySomethingFail();
}
console.log('map result');
return new AppActions.SaySomethingSuccess(result);
}));
observable.subscribe(_ => {
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js"></script>
I wouldn't try to keep the error information throughout the pipe. Instead you should separate your success pipeline (tap, map) from your error pipeline (catchError) by adding all operators to the observable whose result they should actually work with, i.e. your inner observable.
public saySomething$: Observable<Action> = createEffect(() => {
return this.actions.pipe(
ofType<AppActions.SaySomething>(AppActions.SAY_SOMETHING),
switchMap((action) => this.service.saySomething(action.payload).pipe(
tap((result: Result) => {
// Do something with the result!
}),
// Update the store state.
map((result: Result) => {
return new AppActions.SaySomethingSuccess(result);
}),
catchError((error) => {
// I can access the **error** here.
return of(new AppActions.SaySomethingFail());
})
)),
);
});
This way tap and map will only be executed on success results from this.service.saySomething. Move all your error side effects and error mapping into catchError.

Re-execute async RxJS stream after delay

I'm using RxJS 6 to lazily step through iterable objects using code similar to example running below. This is working well but I'm having trouble solving my final use case.
Full code here
import { EMPTY, defer, from, of } from "rxjs";
import { delay, expand, mergeMap, repeat } from "rxjs/operators";
function stepIterator (iterator) {
return defer(() => of(iterator.next())).pipe(
mergeMap(result => result.done ? EMPTY : of(result.value))
);
}
function iterateValues ({ params }) {
const { values, delay: delayMilliseconds } = params;
const isIterable = typeof values[Symbol.iterator] === "function";
// Iterable values which are emitted over time are handled manually. Otherwise
// the values are provided to Rx for resolution.
if (isIterable && delayMilliseconds > 0) {
const iterator = values[Symbol.iterator]();
// The first value is emitted immediately, the rest are emitted after time.
return stepIterator(iterator).pipe(
expand(v => stepIterator(iterator).pipe(delay(delayMilliseconds)))
);
} else {
return from(values);
}
}
const options = {
params: {
// Any iterable object is walked manually. Otherwise delegate to `from()`.
values: ["Mary", "had", "a", "little", "lamb"],
// Delay _between_ values.
delay: 350,
// Delay before the stream restarts _after the last value_.
runAgainAfter: 1000,
}
};
iterateValues(options)
// Is not repeating?!
.pipe(repeat(3))
.subscribe(
v => {
console.log(v, Date.now());
},
console.error,
() => {
console.log('Complete');
}
);
I'd like to add in another option which will re-execute the stream, an indefinite number of times, after a delay (runAgainAfter). I'm having trouble composing this in cleanly without factoring the result.done case deeper. So far I've been unable to compose the run-again behavior around iterateValues.
What's the best approach to accomplish the use case?
Thanks!
Edit 1: repeat just hit me in the face. Perhaps it means to be friendly.
Edit 2: No, repeat isn't repeating but the observable is completing. Thanks for any help. I'm confused.
For posterity here is the full code sample for a revised edition is repeat-able and uses a consistent delay between items.
import { concat, EMPTY, defer, from, interval, of, throwError } from "rxjs";
import { delay, expand, mergeMap, repeat } from "rxjs/operators";
function stepIterator(iterator) {
return defer(() => of(iterator.next())).pipe(
mergeMap(result => (result.done ? EMPTY : of(result.value)))
);
}
function iterateValues({ params }) {
const { values, delay: delayMilliseconds, times = 1 } = params;
const isIterable =
values != null && typeof values[Symbol.iterator] === "function";
if (!isIterable) {
return throwError(new Error(`\`${values}\` is not iterable`));
}
// Iterable values which are emitted over time are handled manually. Otherwise
// the values are provided to Rx for resolution.
const observable =
delayMilliseconds > 0
? defer(() => of(values[Symbol.iterator]())).pipe(
mergeMap(iterator =>
stepIterator(iterator).pipe(
expand(v => stepIterator(iterator).pipe(delay(delayMilliseconds)))
)
)
)
: from(values);
return observable.pipe(repeat(times));
}
I'm gonna be honest, but there could be better solution for sure. In my solution, I ended up encapsulating delay logic in a custom runAgainAfter operator. Making it an independent part, that doesn't affect your code logic directly.
Full working code is here
And the code of runAgainAfter if anybody needs it:
import { Observable } from "rxjs";
export const runAgainAfter = delay => observable => {
return new Observable(observer => {
let timeout;
let subscription;
const subscribe = () => {
return observable.subscribe({
next(value) {
observer.next(value);
},
error(err) {
observer.error(err);
},
complete() {
timeout = setTimeout(() => {
subscription = subscribe();
}, delay);
}
});
};
subscription = subscribe();
return () => {
subscription.unsubscribe();
clearTimeout(timeout);
};
});
};
Hope it helps <3

Chain Observable Queue

Coming from the Promise world, I can implement a queue function that returns a Promise that won't execute until the previous Promise resolves.
var promise = Promise.resolve();
var i = 0;
function promiseQueue() {
return promise = promise.then(() => {
return Promise.resolve(++i);
});
}
promiseQueue().then(result => {
console.log(result); // 1
});
promiseQueue().then(result => {
console.log(result); // 2
});
promiseQueue().then(result => {
console.log(result); // 3
});
// -> 1, 2, 3
I'm trying to recreate this queue-like function using Observables.
var obs = Rx.Observable.of(undefined);
var j = 0;
function obsQueue() {
return obs = obs.flatMap(() => {
return Rx.Observable.of(++j);
});
}
obsQueue().subscribe(result => {
console.log(result); // 1
});
obsQueue().subscribe(result => {
console.log(result); // 3
});
obsQueue().subscribe(result => {
console.log(result); // 6
});
// -> 1, 3, 6
Every time I subscribe, it re-executes the history of the Observable, since at the time of subscription the "current Observable" is actually an Observable which emits multiple values, rather than the Promise that just waits until the last execution has completed.
flatMap isn't the answer for this use case, and nearly all the "chain" and "queue" answers I can find online are about chaining several Observables that are part of one overall Observable, where flatMap is the correct answer.
How can I go about creating the above Promise queue function using Observables?
For context, this queue function is being used in a dialog service, which dictates only one dialog can be shown at a time. If multiple calls are made to show different dialogs, they only appear one at a time in the order that they were called.
If you change:
return obs = obs.flatMap...
With
return obs.flatMap...
You will see the same output as you do with promises (1, 2, 3).
To chain observables such that the next one is not executed until the previous one is complete, use the concat operator
let letters$ = Rx.Observable.from(['a','b','c']);
let numbers$ = Rx.Observable.from([1,2,3]);
let romans$ = Rx.Observable.from(['I','II','III']);
letters$.concat(numbers$).concat(romans$).subscribe(e=>console.log(e));
//or...
Rx.Observable.concat(letters$,numbers$,romans$).subscribe(e=>console.log(e));
// results...
a b c 1 2 3 I II III
Live demo
Figured it out! May not be quite as elegant as the Promise chain, and I'm definitely open to suggestions to clean it up.
var trigger = undefined;
function obsQueue() {
if (!trigger || trigger.isStopped) {
trigger = new Rx.Subject();
return createObservable(trigger);
} else {
var lastTrigger = trigger;
var newTrigger = trigger = new Rx.Subject();
return lastTrigger.last().mergeMap(() => {
return createObservable(newTrigger);
});
}
}
var j = 0;
function createObservable(trigger) {
// In my use case, this creates and shows a dialog and returns an
// observable that emits and completes when an option is selected.
// We want to make sure we only create the next dialog when the previous
// one is closed.
console.log('creating');
return new Rx.Observable.of(++j).finally(() => {
trigger.next();
trigger.complete();
});
}
obsQueue().subscribe(result => {
console.log('first', result);
});
obsQueue().subscribe(result => {
console.log('second', result);
});
obsQueue().subscribe(result => {
console.log('third', result);
});
var timer = setTimeout(() => {
obsQueue().subscribe(result => {
console.log('fourth', result);
});
}, 1000);
// Output:
// creating
// first 1
// creating
// second 2
// creating
// third 3
// creating
// fourth 4
Rather than try to figure out how to chain them in order, I have each observable create its own trigger to let the next observable know when to create itself.
If all the triggers have been completed (setTimeout case, we queue up another one later), then the queue starts again.

Dojo on (events) firing on declaration

While using the new Dojo Event (on) i'm getting a lot of loops.
I'm calling for the first time as doShowSomeDialog(null).
Why does this function get into a loop?
(dialog has been declared before as dijit/Dialog)
doShowSomeDialog = function ( value ) {
var selectName = 'selector';
if ( value ) {
dialog.set("href", "/url/"+ selectName +"/"+ value );
} else {
dialog.set("href", "/url");
dialog.show();
}
dialog.set("onDownloadEnd", function() {
on( dijit.byId(selectName ), "change", doShowSomeDialog( dijit.byId( selectName ).get('value') ) );
}); }
It seems that "on" executes on the declaration of the event.
This will execute the method when calling on. You should wrap it in it's own function.
doShowSomeDialog( dijit.byId( selectName ).get('value')
should become
function() { doShowSomeDialog( dijit.byId( selectName ).get('value'); }

Resources