I am definitely sure I am confused here so please any help is appreciated.
Here is my scenario:
I pull from Firestore a document:
return this.afs.collection("events").doc(eventID).snapshotChanges().pipe(
map( document => {
})
);
All is fine up to here.
But inside the map I need a promise to resolve (or not)
For example:
return this.afs.collection("events").doc(eventID).snapshotChanges().pipe(
map( document => {
// This is a promise the below part
const data = await EventImporterJSON.getFromJSON(document.payload.data())
return data
})
);
I understand that the await cannot happen there. I am very confused how to solve this, perhaps I have not worked long enough with observables and rxjs.
In the end what I am trying to achieve is:
Get the document. Map and process it but inside the process, I need to handle a promise.
I don't want to return that promise to the caller though.
Does this make sense?
Or have I structured this completely wrong?
This is a typical use-case for mergeMap or concatMap:
return this.afs.collection("events").doc(eventID).snapshotChanges().pipe(
mergeMap(document => {
// This is a promise the below part
return EventImporterJSON.getFromJSON(document.payload.data())
})
);
However, you can also use async - await because operators such as mergeMap handle Observables, Promises, arrays, etc. the same way, so you can just return a Promise in mergeMaps project function it will work fine.
Typically, you don't need to use multiple awaits in a single method because the more "Rx" way of doing things is chaining operators, but if you want, you can because the async method returns a Promise and RxJS will handle it like any other Promise.
const delayedPromise = () => new Promise(resolve => {
setTimeout(() => resolve(), 1000);
})
of('a').pipe(
mergeMap(async v => {
console.log(1);
await delayedPromise();
console.log(2);
await delayedPromise();
console.log(3);
await delayedPromise();
return v;
})
).subscribe(console.log);
// 1
// 2
// 3
// a
Live demo: https://stackblitz.com/edit/rxjs-3fujcs
Observables can be seen as a layer up to promises, why don't you use your promise this way ?
like this :
let getDataFromJson(payloadData){
return from(EventImporterJSON.getFromJSON(payloadData());
}
return this.afs.collection("events").doc(eventID).snapshotChanges().pipe(
map(document=>document.payload.data),
switchMap( payloadData=> getDataFromJson(payloadData)))
.subscribe(result=>{
//final result
});
1 pipe your first observable with map just to simplify your returner value
2 switchMap to another observable which will be your promise as an Observable ( with the "from" operator);
The map operator is made for improve result in synchronous and "pure" way like return only few properties of an object or filter a data, here you want to chain two async operation so I suggest you to keep it in a rx approach
Related
I'm trying to resolve an array of promises in the build process of a NextJS app since I'm getting network errors when using Promise.all, but for some reason I'm having trouble resolving the promises.
This code works, but does not work with my build:
const activityPlacesPromises = activityLocationIDs.map((id) =>
this.get(`places/${id}`)
);
const activityPlaces = await Promise.all(activityPlacesPromises);
console.log(activityPlaces); // Returns the data correctly
This code does not work:
const activityPlaces = activityLocationIDs.reduce((promise, id) => {
return promise.then(() => this.get(`places/${id}`));
}, Promise.resolve());
console.log(activityPlaces); // Returns Promise { <pending> }
Why isn't the Promise.resolve() reduce function working?
PS: I am going off of this SO question: Resolve promises one after another (i.e. in sequence)?
activityPlace is still just a promise you will need to await
console.log(await activityPlaces);
Note that you're not doing anything with the result of each promise (aside from the last one)
Wouldn't it be way easier to just throw this in a regular for loop and await one by one? the reduce pattern is useful for cases where you don't have async/await, but you don't seem to have this limitation:
const results = [];
for(const id of activityLocationIDs) {
results.push(await this.get(`places/${id}`));
}
console.log(result);
The above code matches the behavior of your first sample.
It looks like you are trying to avoid using async/await, firstly perhaps you should catch any errors, to allow logging and execution to continue:
const activityPlaces = activityLocationIDs.reduce((promise, id) => {
return promise
.then(() => this.get(`places/${id}`))
.catch((err) => console.error(`Error getting place ID: ${id}`, err))
}, Promise.resolve());
activityPlaces.then(() => console.log(activityPlaces));
Also, consider that since your code is no longer using async, your build may not wait for the promises to resolve before ending.
I'm trying to resolve a promise with the first value emitted since subscribing to an Observable.
I first tried using .toPromise():
await observable.toPromise()
but that only works when observer.complete() is called within the observable.
take(1) and first() also aren't suitable because they just allow the values to be piped to other observables.
At the moment, I've come up with this code:
await new Promise(resolve => {
const subscription = observable.subscribe(data => {
resolve(data)
subscription.unsubscribe()
})
})
Is there a utility function that I'm not using or is there a way to simplify it further?
You need to use the first operator to have the observable emit the first value and complete.
await observable.pipe(first()).toPromise()
This question already has answers here:
How do I access previous promise results in a .then() chain?
(17 answers)
Closed 5 years ago.
I am writing a Node.js script to populate an SQL database with a test dataset.
In the promise chain illustrated in the code snippet below (the real code is a bit more hairy), the function insertData() requires the db object to be passed through from the previous stage. However, asynchronous calls inside dropAndCreateTables() on the previous stage use db object but do not return it. I came up with a solution that wraps promises inside dropAndCreateTables() into another promise object that resolves to a db object. However:
I heard that using Promise() constructor in non-library code is an antipattern and may lead to subtle and hard-to-diagnose mistakes
I heard that nesting then()-chains is also an antipattern
It does not allow me to ignore errors from the promiseDrop (for example, I don't care if tables don't exist on drop)
It is ugly
Questions:
Is there a simpler, nicer and more socially accepted way to override the return value of a promise? (in this case, created with Promise.all())
Is there a way to restructure my code in a way that this problem does not occur? (That is, I don't exclude the possibility of "XY problem" here)
Code:
const dropAndCreateTables = (db, startClean) => {
if(startClean) {
const sqlDrop = fs.readFileSync('drop.sql').toString()
const promiseDrop = db.raw(sqlDrop)
const sqlCreate = fs.readFileSync('create.sql').toString()
const promiseCreate = db.raw(sqlCreate)
/********* Problems here? ************************************/
return new Promise((resolve, reject) => { // Ew?
Promise.all([promiseDrop, promiseCreate])
.then(() => {
resolve(db) // Override the returned value
})
.catch(reject)
})
}
return Promise.resolve(db)
}
initDB({ debug: false })
.then((db) => {
return dropAndCreateTables(db, START_CLEAN) // Without my hack this does not return `db`
})
.then((db) => {
return insertData(db, DO_UPSERT) // This needs the `db` object
})
.then(() => {
console.info(`\n${timestamp()} done`)
})
.catch(handleError)
(Some fairly important notes midway and later in the answer, please do read all the way to the end.)
Is there a simpler, nicer and more socially accepted way to override the return value of a promise? (in this case, created with Promise.all())
Yes, you simply return a value from the then handler, and return the promise then returns:
return Promise.all([promiseDrop, promiseCreate])
.then(() => db);
then (and catch) create promise chains. Each link in the chain can transform the result. then and catch return a new promise that will be fulfilled or rejected based on what happens in their callback:
If their callback throws, the promise rejects with the error thrown
If their callback returns a non-thenable value (e.g., promise), the promise is fulfilled with that value
If their callback returns a thenable value, the promise is resolved to that thenable — it waits for the other promise to settle, then settles the same way
(If the term "thenable" isn't familiar, or you're not clear on the distinction between "fulfill" and "resolve," I go into promise terminology in this post on my blog.)
I heard that using Promise() constructor in non-library code is an antipattern and may lead to subtle and hard-to-diagnose mistakes
The distinction isn't library code vs. non-library code, it's between code that doesn't already have a promise to work with and code that does. If you already have a promise to work with, you almost never want to use new Promise. More: What is the explicit promise construction antipattern and how do I avoid it?
I heard that nesting then()-chains is also an antipattern
You almost never need to nest then chains, because again each link in the chain already has the means of tranforming the result passing through it. So:
// Unnecessary nesting
doSomething()
.then(a => {
return doSomethingElse(a * 2)
.then(b => b * 3);
})
.catch(e => { /*...handle error...*/ });
can be more idiomatically and simply written:
doSomething()
.then(a => doSomethingElse(a * 2))
.then(b => b * 3);
.catch(e => { /*...handle error...*/ });
Is there a way to restructure my code in a way that this problem does not occur? (That is, I don't exclude the possibility of "XY problem" here)
Not an X/Y per se, but you have a problem in that code: There's no guarantee the drop will happen before the create! So instead of starting both and letting them run in parallel and watching for the results with Promise.all, ensure those operations happen in sequence:
// Fairly minimal changes
const dropAndCreateTables = (db, startClean) => {
if(startClean) {
const sqlDrop = fs.readFileSync('drop.sql').toString()
return db.raw(sqlDrop)
.then(() => {
const sqlCreate = fs.readFileSync('create.sql').toString()
return db.raw(sqlCreate);
})
.then(() => db);
}
return Promise.resolve(db)
}
But, I wouldn't use sync file I/O. Instead
const promisify = require("utils").promisify;
const readWithPromise = promisify(fs.readFile);
and then
const dropAndCreateTables = (db, startClean) => {
if(startClean) {
const getDrop = readWithPromise('drop.sql'); // Start this first
const getCreate = readWithPromise('create.sql'); // Then start this
return getDrop
.then(dropSql => db.raw(dropSql)) // Got the drop SQL, run it
.then(() => getCreate) // Make sure we have the create SQl
.then(createSql => db.raw(createSql)) // Run it
.then(() => db);
}
return Promise.resolve(db)
}
Note how we avoid ever busy-waiting on I/O, and we can overlap the DB's drop operation with reading the create SQL.
You don't need to call the Promise constructor when returning another promise, you can just write it like:
return Promise.all([promiseDrop, promiseCreate])
.then(() => db)
.catch(error => {
// handle the error or rethrow it
})
You might omit resolving db from dropAndCreateTables like this:
.then((db) => {
return dropAndCreateTables(db, START_CLEAN).then(Promise.resolve(db));
})
You should not let dropAndCreateTables return a db promise, there is no real usecase for it. So:
return Promise.all([promiseDrop, promiseCreate]);
is enough. Now the chaining part:
initDB({ debug: false }).then(async (db) => {
await dropAndCreateTables(db, START_CLEAN);
await insertData(db, DO_UPSERT);
console.info(`\n${timestamp()} done`)
}).catch(handleError)
I run into this every now and then:
return somethingThatReturnsAPromise()
.then((response) => {
soSomethingg(); // Eg; update the UI
return response;
});
Now I'm looking for something that is not expected to return anything and won't change the promise chain if I forget that:
return somethingThatReturnsAPromise()
.whatImLookingFor((response) => {
doSomething(); // Eg; update the UI
})
.then((response) => {
// and this one should still be able to access response
});
Maybe this goes against the idea of promises, but for me, it's a bit inconvenient since I can't pass arbitrary functions.
One idea is to compose a function:
const sideEffect = (callback) => {
return (response) => {
callback(response);
return response;
};
};
And I could use it as
return somethingThatReturnsAPromise()
.then(sideEffect(doSomething));
But I'd prefer something instead of then is there something like that?
Note: I'm working with Angular 1.x so I need something like for that.
I would assume that you're not really writing .then().then(), because you could collapse that into a single .then, but that your concern is really about returning the promise and having some external code add another then to the chain. In that case do this:
let p = somethingThatReturnsAPromise();
p.then(() => doSomething());
return p;
This allows the caller to attach additional thens to the original promise instead of chaining off of your .then, thereby receiving the original promise's value. This is called branching the promise chain.
Maybe this goes against the idea of promises
Slightly, promise chains are pipelines where then handlers transform things at each stage. But it's perfectly valid to want to pass through the value unchanged.
One idea is to compose a function:
Indeed the first thing that came to mind, and how I'd do it.
But I'd prefer something instead of then is there something like that?
There isn't. You could add it for your own projects (I wouldn't in a library) by adding it to Promise.prototype. Or you could give yourselve a Promise subclass and add it there.
With a Promise sublass you'd do something like:
return MyPromise.resolve(somethingThatReturnsAPromise())
.thenSide(soSomethingg); // Eg; update the UI
...where thenSide is your method that's then but passing the original value back unchanged, e.g.:
class MyPromise extends Promise {
thenSide(callback) {
this.then(callback);
return this;
}
}
or
class MyPromise extends Promise {
thenSide(callback) {
this.then(callback);
return MyPromise.resolve(this);
}
}
...depending on whether you're bothered about thenSide returning the same promise (since then always returns a new one).
As far as I know (I could well be wrong) the wrapper method for "pass-through" side-effects is an idiomatic way to do what you want.
Alternatively (if you need the same response in multiple places) you can break up the promise chain when you encounter a situation like this.
I would like to to something like:
this._myService.doSomething().subscribe(result => {
doSomething()
});
.then( () => dosthelse() )
.then( () => dosanotherthing() )
So I would like to chain .then like in promise. How would I do that in Rxjs?
this._myService.getLoginScreen().subscribe( result => {
window.location.href = MyService.LOGIN_URL;
/// I would like to wait for the site to load and alert something from the url, when I do it here it alerts the old one
});
.then (alert(anotherService.partOfTheUrl())
getLoginScreen() {
return this.http.get(myService.LOGIN_URL)
.flatMap(result => this.changeBrowserUrl())
.subscribe( result => //i want to do sth when the page is loaded//);
}
changeBrowserUrl(): Observable<any> {
return Observable.create( observer => {
window.location.href = myService.LOGIN_URL;
observer.next();
});
}
The equivalent of then for observables would be flatMap. You can see some examples of use here :
RxJS Promise Composition (passing data)
Why we need to use flatMap?
RxJS sequence equvalent to promise.then()?
For your example, you could do something like :
this._myService.doSomething()
.flatMap(function(x){return functionReturningObservableOrPromise(x)})
.flatMap(...ad infinitum)
.subscribe(...final processing)
Pay attention to the types of what your functions return, as to chain observables with flatMap you will need to return a promise or an observable.
If dosthelse or dosanotherthing returns a raw value, the operator to use is map. If it's an observable, the operator is flatMap (or equivalent).
If you want to do something imperatively. I mean outside the asynchronous processing chain, you could leverage the do operator.
Assuming that dosthelse returns an observable and dosanotherthing a raw object, your code would be:
this._myService.doSomething()
.do(result => {
doSomething();
})
.flatMap( () => dosthelse() )
.map( () => dosanotherthing() );
Notice that if you return the return of the subcribe method, it will correspond to a subscription object and not an observable. A subscription object is mainly for being able to cancel the observable and can't take part of the asynchronous processing chain.
In fact, most of the time, you subscribe at the end of the chain.
So I would refactor your code this way:
this._myService.getLoginScreen().subscribe( result => {
window.location.href = MyService.LOGIN_URL;
/// I would like to wait for the site to load and alert something from the url, when I do it here it alerts the old one
alert(anotherService.partOfTheUrl()
});
getLoginScreen() {
return this.http.get(myService.LOGIN_URL)
.flatMap(result => this.changeBrowserUrl())
.do( result => //i want to do sth when the page is loaded//);
}
changeBrowserUrl(): Observable<any> {
return Observable.create( observer => {
window.location.href = myService.LOGIN_URL;
observer.next();
});
}
Updated rxjs solution
Rxjs has changed quite a bit since this was answered.
flatMap is now mergeMap
Or switchMap, they're mostly interchangeable but it's good to know the difference
.do() is now tap()
Chaining is now done inside of a .pipe(). All manipulation should be done inside this pipe
You can chain pipes if needed (Ex. one variable maps an array of Users. Another variable takes that first variable and maps it a second time)
Do something after the original call has been made
Scenario
Make an HTTP call (Ex. Authentication check)
When that call has finished, navigate to another page
this._myService.getAuthenticated()
.pipe(
tap(result => this._myService.navigateToHome())
)
.subscribe()
Chain multiple calls
Scenario
Make an HTTP call (Ex. Authentication check)
Make a 2nd call to pull more info
Navigate after both calls have finished
this._myService.getAuthenticated()
.pipe(
// The Authentication call returns an object with the User Id
switchMap(user => this._myService.getUserInfo(user.id))
// After the user has been loaded, navigate
tap(user => this._myService.navigateToHome())
)
.subscribe()
Note on the above examples: I am assuming these calls are HTTP which unsubscribe after being called once. If you use a live observable (ex. a stream of Users), make sure you either unsubscribe or use takeUntil/first operators.
Example for Clarification (April, 2022)
The top of this pipe can emit n values (this means the chain will be called everytime a new value enters into the top of the pipe). In this example, n equals 3. This is a key difference between observables and promises. Observables can emit multiple values over time, but a promise cannot.
The 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 firstObservable = of(1);
const secondObservable = of(2);
const thirdObservable = of(3);
const fourthObservable = of(4);
const addToPreviousStream = (previous) => map(current => previous + current);
const first = (one) => firstObservable.pipe(addToPreviousStream(one));
const second = (two) => secondObservable.pipe(addToPreviousStream(two));
const third = (three) => thirdObservable.pipe(addToPreviousStream(three));
const fourth = (four) => fourthObservable.pipe(addToPreviousStream(four));
// Pipeline of mergeMap operators, used for chaining steams together.
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