Variable Assignment of Nested Promise Not Resolving (Stripe API) - javascript

Using the Stripe API, I am calling all products stripe.products.list(), and then attempting to append pricing data stripe.prices.list() for each product using the map() function. Each call returns a promise, although the nested promise is not resolving, and is instead returning an empty prices object. What am I missing?
export function handler(event, context, callback) {
stripe.products
.list()
.then(products =>
products.data.map(product => ({
...product,
prices: stripe.prices // <-- ASSIGNMENT NOT RESOLVED
.list({ product: product.id })
.then(prices => {
console.log(prices) // <-- RESOLVED IN CONSOLE
return prices
}),
}))
)
.then(rsp => {
callback(null, {
headers: { 'Content-Type': 'application/json' },
statusCode: 200,
body: JSON.stringify(rsp),
})
})
.catch(err => console.warn(err))
}

You are not awaiting for the internal promise to resolve:
export function handler(event, context, callback) {
stripe.products
.list()
.then(products =>
return Promise.all(products.data.map(product => {
return stripe.prices // <-- ASSIGNMENT NOT RESOLVED
.list({ product: product.id })
.then(prices => {
console.log(prices) // <-- RESOLVED IN CONSOLE
return prices
}),
}))
.then(prices => {
return {
...product,
prices
};
});
)
.then(rsp => {
callback(null, {
headers: { 'Content-Type': 'application/json' },
statusCode: 200,
body: JSON.stringify(rsp),
})
})
.catch(err => console.warn(err))
}

Array.map is executed synchronous and you are not awaiting the promises within products.data.map. The prices object is not empty, it is a promise. The output in the console is generated only after the handler has already returned.
A simple way, would be the following. Ie store the received products in a variable. Then use Promise.all() to get the prices for all of the products. And then combine those two arrays for the final result.
export function handler(event, context, callback) {
let theproducts;
stripe.products
.list()
.then(products => {
theproducts = products.map(p => p.data);
return Promise.all(products.data.map(product =>
stripe.prices.list({ product: product.id }))
)
})
.then(prices => theproducts.map((p, i) => ({...p, prices: prices[i]})
.then(rsp => {
callback(null, {
headers: { 'Content-Type': 'application/json' },
statusCode: 200,
body: JSON.stringify(rsp),
})
})
.catch(err => console.warn(err))
}
You can also merge products and price immediately when stripe.prices.list() resolves and so spare an additional iteration of the map. But I personally find it more readable this way. And if you don't have too many products, the additonal iteration doesn't take too long.

With lots of helpful input, I ended up with the following.
export function handler(event, context, callback) {
stripe.products
.list()
.then(products => {
return Promise.all(
products.data.map(product =>
stripe.prices
.list({ product: product.id })
.then(prices => {
return prices
})
.then(prices => {
return {
...product,
prices,
}
})
)
)
})
.then(rsp => {
callback(null, {
headers: { 'Content-Type': 'application/json' },
statusCode: 200,
body: JSON.stringify(rsp),
})
})
.catch(err => console.warn(err))
}

You have returned the array within products.data.map. However this is an array of promises. When you map them and chain the promise. It does not actually resolve the promise. I suggest you try some async syntax to simplify the code.
(async () => {
const blah = products.data.map(product => {
const prices = await stripe.prices.list({ product: product.id });
return { ...product, prices };
});
console.log(blah);
})()

Related

Return resolved promise value

const displayCharacters = async () => {
if(filteredCharacters !== 'default'){
const a = filteredCharacters.map(e => e.name);
const options = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ 'data' : a })
};
const b = await fetch("/image",options).then(res => res.json())
return b;
}else{
return "yikes";
}
}
console.log(displayCharacters());
I have this fetch request but when I log the results this is what i see :
Promise {<resolved>: "yikes"}
__proto__: Promise
[[PromiseStatus]]: "resolved"
[[PromiseValue]]: "yikes"
I just want the promiseValue and not this whole thing around it. How do i do this?
the async function returns a promise instantly, without waiting for the promise to resolve. You may instead console.log inside the function:
const displayCharacters = async () => {
if(filteredCharacters !== 'default'){
const a = filteredCharacters.map(e => e.name);
const options = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ 'data' : a })
};
try {
const b = await fetch("/image",options).then(res => res.json());
console.log(b);
//the better practice is however, to do like:
const b = await fetch("/image",options)
const result = await b.json();
console.log(result );
}
catch(err) {
console.log(err);
}
}else{
console.log("yikes");
}
}
displayCharacters();
The best way that i know to use fetch goes something like this:
const displayCharacters = async () => {
if(filteredCharacters !== 'default'){
const a = filteredCharacters.map(e => e.name);
const options = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ 'data' : a })
};
const b = await fetch("/image",options)
.then(res => {
// Handle API Errors
if (!res.ok) {
throw Error(res.statusText);
}
// Return if no errors
return res.json();
})
// this is the data you want
.then(data => data)
// it will only reject on network failure or if anything prevented the request from completing
.catch(error => {
console.log(error.message)
});
return b;
}else{
return "yikes";
}
}
Basically you chain two thens and a catch to completely understand the response
- the first then checks for api level errors
- second then gets you the data
- catch is invoked in case when it is not able to reach the api itself like connection issues

How do we get value from inside the arrow function on outer scope?

I have the following code which requests from api and gets data in response. How can I get assigned variables inside arrow function i.e. priceEvents, defaultDate etc after all the api request and set state after that? Is it a good approach on setting state after all the api requests?
axios.get(`/api/v1/test/prices?date_from=${ currentDate.format() }`, { headers: headers })
.then((response) => {
const priceEvents = response.data;
const defaultDate = this.cal.$calendar.fullCalendar("getDate");
//this.setState({ priceEvents, defaultDate });
})
.then((response) => {
return axios.get(`/api/v1/test/events?date_from=${ currentDate.format() }`, { headers: headers })
})
.then((response) => {
const bookingEvents = response.data;
//this.setState({ bookingEvents });
});
console.log(bookingEvents);
console.log(priceEvents);
You need to pass the values along from one promise to another:
axios.get(`/api/v1/test/prices?date_from=${ currentDate.format() }`, { headers: headers })
.then((response) => {
return {priceEvents: response.data}
})
.then(({priceEvents}) => {
return axios.get(`/api/v1/test/events?date_from=${ currentDate.format() }`, { headers: headers })
.then(response => {
return {priceEvents, bookingEvents: response.data}
})
})
.then(({priceEvents, bookingEvents}) => {
const defaultDate = this.cal.$calendar.fullCalendar("getDate");
this.setState({ priceEvents, bookingEvents, defaultDate });
console.log(bookingEvents);
console.log(priceEvents);
});
You could also use await. It's a bit cleaner in this situation.
async function doStuff() {
const {data: priceEvents} = await axios.get(`/api/v1/test/prices?date_from=${ currentDate.format() }`, { headers: headers })
const {data: bookingEvents} = await axios.get(`/api/v1/test/events?date_from=${ currentDate.format() }`, { headers: headers })
const defaultDate = this.cal.$calendar.fullCalendar("getDate");
this.setState({ priceEvents, bookingEvents, defaultDate }); // 'this' may not refer to your react component in this case
console.log(bookingEvents);
console.log(priceEvents);
}
doStuff()
Also, since the two axios requests don't depend on each other, you could run them in parallel:
Promise.all([
axios.get(`/api/v1/test/prices?date_from=${ currentDate.format() }`, { headers: headers }),
axios.get(`/api/v1/test/events?date_from=${ currentDate.format() }`, { headers: headers }),
])
.then(([priceResponse, eventsResponse]) => {
return {
priceEvents: priceResponse.data,
bookingEvents: eventsResponse.data,
}
})
.then(({priceEvents, bookingEvents}) => {
const defaultDate = this.cal.$calendar.fullCalendar("getDate");
this.setState({ priceEvents, bookingEvents, defaultDate });
console.log(bookingEvents);
console.log(priceEvents);
});

Chaining synchronous Redux actions and consuming in component

I have two Redux actions which need to execute synchronously. requestStripeToken is called in my component (signupComponent.js), but in order to get a Stripe Token I first need to make a call to an internal API to get the current Stripe Key (as this changes depending on the environment and SKU). Both of these functions are set up as separate actions in my actions file (actions.js).
The issue I have it that I am not sure how to consume the requestStripeToken function in my component. I don't know whether it is an issue with what I am returning in the requestStripeToken action or whether the Promise consumption logic in my component needs to change. Note I am using redux-thunk middleware.
// actions.js
export function requestStripeToken(values) {
return function(dispatch) {
const { cardNumber, cvc, nameOnCard, expiryMonth, expiryYear, billingLine1, billingLine2, billingCity, billingState, billingZip, billingCountry } = values;
// We need to get the Stripe key before we can request a Stripe Token
return dispatch(getStripeSecretKey())
// Curried function necessary as getStripeSecretKey returns the fetch Promise inside of function(dispatch) ?
.then(() => (key) => {
console.log(key);
return new Promise((resolve, reject) => {
Stripe.setPublishableKey(key);
Stripe.card.createToken({
number: cardNumber,
cvc,
name: nameOnCard,
exp_month: expiryMonth,
exp_year: expiryYear,
address_line1: billingLine1,
address_line2: billingLine2,
address_city: billingCity,
address_state: billingState,
address_zip: billingZip,
address_country: billingCountry,
}, (status, response) => {
if (response.error) {
dispatch(addNotification({
message: response.error.message,
level: `error`,
autoDismiss: 0,
}));
reject();
}
return resolve(response.id);
});
});
});
};
}
export function getStripeSecretKey() {
return function(dispatch) {
return fetch(`${getAPIPath}api/stripeKey`, {
method: `GET`,
credentials: `include`,
headers: {
Accept: `application/json`,
},
})
.then(handleErrors)
.then((response) => {
response.json().then((res) => {
return res.data;
});
})
.catch(response => response.json().then((res) => {
dispatch(addNotification({
message: res.message,
level: `error`,
autoDismiss: 0,
}));
throw res;
}));
};
}
console.log(key) in this file never gets called.
// signupComponent.js
handleCreateAccountSubmit = (values) => {
this.setState({ submitting: true });
// We need the Stripe Token before we can signup the user so needs to be synchronous
this.props.actions.requestStripeToken(values)
.then((stripeToken) => {
console.log(stripeToken);
this.signupUser(values, stripeToken);
})
.catch(() => this.stopSubmission());
}
console.log(stripeToken) in this file returns:
ƒ (key) {
console.log(key);
return new Promise(function (resolve, reject) {
Stripe.setPublishableKey(key);
Stripe.card.createToken({
number: cardNumber,
…
You need to return Promises in your getStripeSecretKey() as well.
Dispatch returns what the action creator returns, so if you do:
export function getStripeSecretKey() {
return function(dispatch) {
return fetch(${getAPIPath}api/stripeKey, {
method:GET,
credentials:include,
headers: {
Accept:application/json,
},
})
.then(handleErrors) // also return Promise.reject() in errors
.then((response) => {
return response.json().then((res) => { // DONT BREAK RETURN CHAIN
return Promise.resolve(res.data); // RESOLVE
});
})
.catch(response => response.json().then((res) => {
dispatch(addNotification({
message: res.message,
level:error,
autoDismiss: 0,
}));
return Promise.reject(res); // REJECT
}));
};
}

return undefined fetch inside AsyncStorage

I have a react-native app where I do a call to an api where it should return the JSON but I'm just having undefined.
export function fetchFromAPI() {
AsyncStorage.getItem('#token', (errToken, token) => {
let token = null;
const requestBody = { token: token };
return fetch(url, {
method: 'POST',
body: JSON.stringify(requestBody)
})
.then((response) => response.json())
.then((responseJSON) => {
console.log(responseJSON); // <-- this shows the correct JSON data
return responseJSON;
}).catch((error) => {
// console.error(error);
});
});
}
I also call that funcion like this:
const apiData = fetchFromAPI();
If I do console.log() inside the fetch function, it returns the JSON data but if I do to apiData, it just gets undefined.
Does anyone has some idea why its like this, I'm doing something wrong?
You can use Promise to get response from fetchFromAPI function, like
export function fetchFromAPI() {
return new Promise((resolve, reject) => {
AsyncStorage.getItem('#token', (errToken, token) => {
let token = null;
const requestBody = {
token: token
};
return fetch(url, {
method: 'POST',
body: JSON.stringify(requestBody)
})
.then((response) => response.json())
.then((responseJSON) => {
console.log(responseJSON); // <-- this shows the correct JSON data
resolve(responseJSON);
}).catch((error) => {
reject(error);
});
});
});
}
When calling the fetchFromAPI, use await, like
const apiData = await fetchFromAPI();
You can also use .then to capture the response and store it in the state, like
fetchFromAPI.then((data) => {
// use data here
});
Hope this will help!
First, you need to return the Promise created by getItem:
export function fetchFromAPI() {
return AsyncStorage.getItem('#token', (errToken, token) => {
let token = null;
const requestBody = { token: token };
return fetch(url, {
method: 'POST',
body: JSON.stringify(requestBody)
})
.then((response) => response.json())
.then((responseJSON) => {
console.log(responseJSON); // <-- this shows the correct JSON data
return Promise.resolve(responseJSON); // <-- this wraps the JSON into a Promise
}).catch((error) => {
// console.error(error);
});
});
}
Then you need to call the function like this:
fetchFromAPI().then(apiData => {...

indexOf refuses to work inside vue mutation when action is using promise

I do get some problems getting indexOf inside vuex mutation to work when I use action with a promise. This is my action
export const deleteItem = ({commit}, item) => {
return new Promise((resolve, reject) => {
Vue.http.delete(item.url, item)
.then(response => {
commit(types.DELETE_ITEM, {response, item})
resolve(response)
})
.catch(error => {
commit(types.ERROR, error)
reject(error)
})
})
}
This is my mutation
[types.DELETE_ITEM](state, {response, item}) {
state.notifications = {
display: true,
type: 'success',
ok: response.ok,
status: response.status,
statusText: response.statusText,
body: response.body
}
state.items.splice(state.items.indexOf(item), 1)
}
I am getting an error indexOf is not a function does anyone know why is that?
I was stupid enough to reassign state.items array to an object down in the code and indexOf function disappeared.

Categories

Resources