Why won't my promise.all return data even though it works - promise

I am running the following code:
let Payment = relevantWaitList.map(e => {
stripe.paymentIntents.create({
amount: Math.round(e.totalCharge * 100),
currency: currency,
description: `Resale of ${eventData.title} refunded ticket`,
// customer: customerStripeID,
payment_method: e.paymentMethod,
off_session: true,
confirm: true,
application_fee_amount: Math.round(e.applicationFee*100)
}
,{
stripe_account: organiserStripeAccountID,
}
)
})
Promise.all(Payment)
.then(data => {
console.log('promiseall payment res', data)
}).catch(err => {
console.log('promise all payment fail', err)}
Which is returning the following:
promiseall payment res undefined
Despite it returning undefined, the promise.all is working - the stripe payments intents are created.
When I change to promise to include the .then within the map (using the code below), it console logs fine but I would prefer to play with the data after all promises have been completed.
What am I missing?
let Payment = relevantWaitList.map(e => {
stripe.paymentIntents.create({
amount: Math.round(e.totalCharge * 100),
currency: currency,
description: `Resale of ${eventData.title} refunded ticket`,
// customer: customerStripeID,
payment_method: e.paymentMethod,
off_session: true,
confirm: true,
application_fee_amount: Math.round(e.applicationFee*100)
}
,{
stripe_account: organiserStripeAccountID,
}
)
.then(data => console.log('data within map', data))
.catch(err => console.log('err within map', err))
})

Your .map() callback does not return anything which means that .map() will just return an array of undefined, giving Promise.all() no promises and no data to use.
What you need to be passing Promise.all() is an array of promises that each resolve to a value, then Promise.all() will return a promise that will resolve to an array of those values. In this case, you have garbage into Promise.all() and therefore garbage out.
So, your .map() callback should be returning a promise that resolves to the value you eventually want.
Assuming stripe.paymentIntents.create() returns a promise that resolves to a value you want, you just need to add a return statement:
let Payment = relevantWaitList.map(e => {
// ******* Add return on next line *********
return stripe.paymentIntents.create({
amount: Math.round(e.totalCharge * 100),
currency: currency,
description: `Resale of ${eventData.title} refunded ticket`,
// customer: customerStripeID,
payment_method: e.paymentMethod,
off_session: true,
confirm: true,
application_fee_amount: Math.round(e.applicationFee*100)
} , {stripe_account: organiserStripeAccountID,
});
});

As mentioned in the answer from #jfriend00, your map callback does not return anything.
You need to return the Promise object for each iteration
const payments = relevantWaitList.map(e => {
return stripe.paymentIntents.create({
// your props
});
});
The payments variable now contains an array of promises. You can use Promise.all() now
Promise.all(payments).then(data =>{
// play with data here :)
});

Related

How do I iterate over functions that return rxjs observables

I want to iterate over a series of asynchronous functions and end the iterating when a false is returned.
I'm new to rxjs and can't get the use-case below to work. I feel like I'm not understanding something fundamental. Can someone please point it out to me?
function validateA(): Observable<any> {
// do stuff.
return of({ id: "A", result: true }); // hardcoding result for now
}
function validateB(): Observable<any> {
// do stuff
return of({ id: "B", result: true }); // hardcoding result for now
}
function validateC(): Observable<any> {
// do stuff
return of({ id: "C", result: false });// hardcoding result for now
}
from([validateA, validateB, validateC])
.pipe(
map(data => data()),
takeWhile(data => !!data.result)
)
.subscribe(data => console.log(`${data.id} passed!`));
https://stackblitz.com/edit/typescript-ub9c5r?file=index.ts&devtoolsheight=100
I would say that the core of your logic is right. What is missing is some rxJs pecularity.
The solutions could be something like this. Explanation of the nuances are in the comments.
// start from an array of functions and turn it into a stream using RxJs from function
from([validateA, validateB, validateC])
.pipe(
// now execute each function sequentially, one after the other, via concatMap
// operator. This operator calls each function and each function returns an Observable
// concatMap ensures that the functions are called sequentially and also that the returned Observable (because each function returns an Observable)
// is "flattened" in the result stream. In other words, you execute each function one at the time
// and return the value emitted by the Observable returned by that function
// until that Observable completes. Considering that you use the "of" function to
// create the Observable which is returned by each function, such Observable emits just one value and then completes.
concatMap(func => func()),
// now you have a stream of values notified by the Observables returned by the functions
// and you terminate as soon as a flase is received
takeWhile(data => !!data.result)
)
.subscribe(data => console.log(`${data.id} passed!`));
The following seems to do the trick and calls functions lazily:
https://stackblitz.com/edit/typescript-9ystxv?file=index.ts
import { from, Observable, of } from "rxjs";
import { concatAll, find, map } from "rxjs/operators";
function validateA() {
console.log('validateA');
return of({ id: "A", result: false });
}
function validateB() {
console.log('validateB');
return of({ id: "B", result: true });
}
function validateC() {
console.log('validateC');
return of({ id: "C", result: false });
}
from([validateA, validateB, validateC])
.pipe(
map(validate => validate()),
concatAll(),
find(data => data.result)
)
.subscribe(data => console.log(`${data.id} passed!`));

RxJS - Iterate over returned data from ajax

I am using RxJS Ajax() to return a bunch of GitHub users. I want to iterate over each one and just get the login names from each user. I am able to iterate over a local array of objects, but not if that array of objects came from Ajax, whether or not I force it to be a stream with RxJS from().
// from() the array of objects (locally defined)
let localArrayOfObj = from([
{ login: 'Barbara Holland', loggedIn: false, token: null },
{ login: 'Joyce Byers', loggedIn: true, token: 'abc' },
{ login: 'Nancy Wheeler', loggedIn: true, token: '123' }
]);
// Return the "login" names:
localArrayOfObj.pipe(
map((data) => data.login)
).subscribe(console.log); // This returns the login names
// from() the array of objects (returned by Ajax)
const githubUsers = `https://api.github.com/users?per_page=2`;
const arrayFromAjax = from(ajax.getJSON(githubUsers));
arrayFromAjax.subscribe(console.log);
// Trying the same to return the "login" names:
arrayFromAjax.pipe(
map((data) => data.login)
).subscribe(console.log); // This is undefined
I'm putting the results of the Ajax call through RxJS from() so both arrays are treated the same, however the same undefined results occur if I skip from() and just do
const arrayFromAjax = ajax.getJSON(githubUsers);
instead of
const arrayFromAjax = from(ajax.getJSON(githubUsers));
How is an array of objects from Ajax() different than a similar locally defined array of objects? And how can I iterate over the Ajax results?
The from() operator creates an observable that emits the provided values one at at time, so you are not receiving an array, you are receiving individual objects.
ajax.getJSON(githubUsers) returns an array of objects. This is why you are seeing differences between these two cases.
You can alter your test case with local array to the following the match the same shape:
let localArrayOfObj = from([
[
{ login: 'Barbara Holland', loggedIn: false, token: null },
{ login: 'Joyce Byers', loggedIn: true, token: 'abc' },
{ login: 'Nancy Wheeler', loggedIn: true, token: '123' }
]
]);
In your code here: data is an array, so if you want to modify the elements individually you can do something like:
arrayFromAjax.pipe(
map((dataArray) => dataArray.map(data => data.login))
).subscribe(console.log); // This is undefined

Returning different Promise data with axios

I am trying to work with the Marvel api and using multiple sources of data with an axios.get method. I am at the moment returning with the characters ID to display the characters info and the comics associated with them. I made a function to get the comics using another axios.get which I am trying to assign to a variable so I can pass that to my template engine. I consoled the variable and got [object Promise], when I pass to my engine nothing appears. I am unsure what I am doing wrong and using then() and catch() is kinda confusing me even when I read the documentation.
function getCharactersComic(character){
var charactersComics;
return axios.get('https://gateway.marvel.com:443/v1/public/characters/' + character[0].id + '/comics?limit=10', {
params:{
ts: timeStamp,
apikey: marvelKey.pubKey,
hash: marvelHash
}
})
.then(response =>{
charactersComics = response.data.data.results;
console.log("comics" + charactersComics);
return charactersComics;
})
.catch(err => {
console.log('This is the error from comics: ', err.response);
})
router.get("/:id", function(req,res){
axios.get('https://gateway.marvel.com:443/v1/public/characters/' + req.params.id,{
params: {
ts: timeStamp,
apikey: marvelKey.pubKey,
hash: marvelHash
}
})
.then(response =>{
let character = response.data.data.results;
let comics = getCharactersComic(character);
console.log('inside call ' + comics);
res.render('character', {character: character , comics:comics});
})
.catch(error => console.log('This is the error from id: ', error));
});

Observable from Subject

I'm trying to create actions from updates from a RX Subject
It's working but I get the error below.
Here is my Epic
export function uploadSceneFile(action$, store) {
return action$.ofType(CREATE_SCENE_SUCCESS)
.mergeMap(({payload}) =>
UploadSceneWithFile(payload)
.subscribe(res => {
if (res.progress > 0)
store.dispatch(uploadSceneProgress(res))
else if(res.progress === -1){
store.dispatch(uploadSceneSuccess(res))
requestSceneProcessing(res).map(res => {
})
}
})
)
}
And here is the Subject
export function UploadSceneWithFile(scene){
const subject$ = new Subject()
const uploader = new S3Upload({
getSignedUrl: getSignedUrl,
uploadRequestHeaders: {'x-amz-acl': 'public-read'},
contentType: scene.file.type,
contentDisposition: 'auto',
s3path: 'assets/',
onError:()=>subject$.next('error'),
onProgress: (val)=> subject$.next({...scene,progress:val}),
onFinishS3Put: ()=>subject$.next({...scene,progress:-1}),
})
uploader.uploadFile(scene.file)
return subject$
}
I read from a previous post that I'm supposed to be using .map, not .subscribe but nothing happens if I don't subscribe (the upload doesn't happen)
What's the best way of doing this?
subscribeToResult.js:74 Uncaught TypeError: You provided an invalid object where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
at Object.subscribeToResult (subscribeToResult.js:74)
at MergeMapSubscriber../node_modules/rxjs/operators/mergeMap.js.MergeMapSubscriber._innerSub (mergeMap.js:132)
at MergeMapSubscriber../node_modules/rxjs/operators/mergeMap.js.MergeMapSubscriber._tryNext (mergeMap.js:129)
at MergeMapSubscriber../node_modules/rxjs/operators/mergeMap.js.MergeMapSubscriber._next (mergeMap.js:112)
at MergeMapSubscriber../node_modules/rxjs/Subscriber.js.Subscriber.next (Subscriber.js:89)
at FilterSubscriber../node_modules/rxjs/operators/filter.js.FilterSubscriber._next (filter.js:89)
at FilterSubscriber../node_modules/rxjs/Subscriber.js.Subscriber.next (Subscriber.js:89)
at Subject../node_modules/rxjs/Subject.js.Subject.next (Subject.js:55)
at createEpicMiddleware.js:60
at createEpicMiddleware.js:59
at SafeSubscriber.dispatch [as _next] (applyMiddleware.js:35)
at
The problem is that you subscribe inside mergeMap and return a Subscription which is invalid. The callback needs to return only Observable, Promise, Array, or Iterable.
I'm not sure what exactly you need to do but if you need to perform some side-effects you can use do() operator instead of subscribing.
export function uploadSceneFile(action$, store) {
return action$.ofType(CREATE_SCENE_SUCCESS)
.mergeMap(({ payload }) => UploadSceneWithFile(payload)
.do(res => {
...
})
)
}
Or it looks like you could put do after mergeMap as well:
export function uploadSceneFile(action$, store) {
return action$.ofType(CREATE_SCENE_SUCCESS)
.mergeMap(({ payload }) => UploadSceneWithFile(payload))
.do(res => {
...
});
}

Making concurrent call in Bluebird promise using Sequelize findOne method . Returns undefined

I want to verify existence of specific user data from multiple tables to make it a concurrent call i am using Bluebird Promise.Prop like given below. Data is acceded Using sequelize ORM.
Promise.props({
user: (()=>{
return User.findOne({
where: {username: req.user.username}
});
}),
comments: (()=>{
return comments.findOne({
where: {username: req.user.username}
});
})
}).then((result)=> {
console.log(result.user.name, result.comments.count);
});
I also tried with a nested promise but doesn't succeeded. like
Promise.props({
user: (()=>{
return User.findOne({
where: {username: req.user.username}
}).then((user)=>{
console.log(user.name); // even here i am getting undefined
});
}),
comments: (()=>{
return comments.findOne({
where: {username: req.user.username}
});
})
}).then((result)=> {
console.log(result.user.name, result.comments.count);
});
You are not clear if result.user is undefined, or result.user.name is undefined.
I expect the latter.
You pass an object with 2 keys to Promise.props.
But both of the keys are a function, and not a promise. So promise.props sees the function, not the promise.
The result should still have the 2 functions.
Try
Promise.props({
user: User.findOne({
where: {username: req.user.username}
}),
comments: comments.findOne({
where: {username: req.user.username}
})
}).then((result)=> {
console.log(result.user.name, result.comments.count);
});
Other good ways are Promise.all, or if you know how many promises you have then use Promise.join
Promise.join(
User.findOne({
where: {username: req.user.username}
}),
comments.findOne({
where: {username: req.user.username}
}),
(user, comment) => {
console.log(user.name, comments.count);
}
);
You are returning the Promise itself rather than the resolve value. The results need to be collected in the promise resolution and an then be passed along.
// collect values in this object
const values = {};
// run all promises
Promise.all([
model.findOne()
.then((val) => {
// assign result 1
values.val1 = val;
return Promise.resolve();
}),
model.findOne()
.then((val) => {
// assign result 2
values.val2 = val;
return Promise.resolve();
}),
])
.then(() => {
// the values will be collected here.
console.log(values);
});

Resources