Using values within different arrays - javascript

I have 2 arrays retrieved from a db and stripe respectively. Both data were mapped using a common field. In attempting to process charges, I get this error StripeInvalidRequestError ---- There is currently another in-progress request using this Stripe token. Meaning the loop definitely runs twice. So my question is how do I efficiently achieve using data from both arrays without looping twice?
Any thoughts, please?
array1 = [
{ totalCharge: 10000 },
{ totalCharge: 30000 },
]
array2 = [
{ customer: 63667363,
source: "kssgjag_98899"
},
{ customer: 767897889
source: "hkhdgd_93762"
},
]
for (let x of array1){
for (let y of array2){
stripe.charges.create({
amount: x.totalCharge,
currency: "usd",
customer: y.id,
source: y.source
}, function(err, charge) {}
}
}

You're only visiting each combination of x and y once (total of four combinations), but from subsequent comments it appears that you don't want to do that, you want to make two charges: 10000 for kssgjag_98899 and 30000 for hkhdgd_93762.
The simplest thing to do is probably build yourself a single array of charges to make, then process it one charge at a time.
Building the array:
if (array1.length !== array2.length) {
throw new Error("Arrays should be of the same length");
}
const charges = array1.map(({totalCharge}, index) {
const {id, source} = array2[index];
return {
amount: totalCharge,
currency: "usd",
customer: id,
source
};
});
Processing them one at a time: Nicolas Takashi told me in a comment on a now-deleted answer that in addition to having a callback, stripe.charges.create also returns a promise. If so, you can use the classic promise reduce trick:
const promiseForResult = charges.reduce(
(p, charge) => p.then(() => stripe.charges.create(charge)),
Promise.resolve()
);
That will ensure that you only have one call to stripe.charges.create happening at any given time.
If it's okay for the calls to overlap (but from your error it sounds like it isn't), you'd use Promise.all instead:
const promiseForResult = Promise.all(
charges.map(charge => stripe.charges.create(charge))
);
If it's not true that stripe.charges.create returns a promise, you can easily give yourself a wrapper that does:
const createCharge = charge => new Promise((resolve, reject) => {
strip.charges.create(charge, function(err, result) {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
and then either one-at-a-time:
const promiseForResult = charges.reduce(
(p, charge) => p.then(() => createCharge(charge)),
Promise.resolve()
);
or all at once:
const promiseForResult = Promise.all(charges.map(createCharge));

Here is a working example that results in a new array array3 that has the zipped up result.
const array1 = [
{ totalCharge: 10000 },
{ totalCharge: 30000 },
]
const array2 = [
{ customer: 63667363,
source: "kssgjag_98899"
},
{ customer: 767897889,
source: "hkhdgd_93762"
},
]
const array3 = [];
for (let x of array1) {
for (let y of array2) {
array3.push({
amount: x.totalCharge,
currency: "usd",
customer: y.customer,
source: y.source
});
}
}
console.log(JSON.stringify(array3));
Output:
[
{"amount":10000,"currency":"usd","customer":63667363,"source":"kssgjag_98899"},
{"amount":10000,"currency":"usd","customer":767897889,"source":"hkhdgd_93762"},
{"amount":30000,"currency":"usd","customer":63667363,"source":"kssgjag_98899"},
{"amount":30000,"currency":"usd","customer":767897889,"source":"hkhdgd_93762"}
]
You will need to make a charge request from your server for each of these, waiting for the previous request to complete before starting the next:
function processCharges(charges: any[], index = 0) {
stripe.charges.create(charges[index], (err, charge) => {
if (err) {
// handle error
}
index++;
if (index < charges.length) {
processCharges(charges, index);
}
});
}

Related

How to wait for the final result of a for loop with API calls?

This loop is going to run an arbitrary amount of times, and I want to get the result out of it after them all. Anything I try (promisifying, async/await, nesting functions, et al) seems to be a dead end. I don't get why I cannot just stick a .then on the API call, or on the function I made here. But I suspect the problem is more fundamental with my understanding, because I can't seem to even get just the "data" to return...same inability to wait on the API call. Wrapping it in a promise loses the "data" and pulling it with the "for loop" inside there doesn't work either. This is making me question my entire progress with JS/implementing other people's APIs.
const gotPeeps = () => {
challongeClient.tournaments.show({
id: tournamentURL,
callback: (err, data) => {
//return data //doesnt return "data" from gotPeeps?
for (const [key, value] of Object.entries(data.tournament.matches)) {
if (value.match.state === 'open') {
peepsList.push(value.match.player1Id, value.match.player2Id)
console.log(peepsList)
}}}})}
gotPeeps()
EDIT
To the comments:
I'm trying to get the results after the for loop is complete.
The "loop" I was referring to is the "for of" over the data Object.
"Putting the code after the loop but inside the callback" does not work. I have a previous question in my week of failing to solve this:
How to solve race condition in Javascript?
Here is the whole thing, with some past versions commented out.:
const tournamentModel = require('../models/tournamentSchema')
require('dotenv').config()
const challonge = require('challonge');
module.exports = {
name: 'getmatches',
aliases: ['gm'],
cooldown: 0,
description: 'Get Challonge data into console.',
execute(message, args, cmd, client, Discord, profileData) {
let peep = ''
let peepsList = ''
const tournamentURL = 'TESTING_Just_Sign_Up_If_You_See_This850587786533011496'
const challongeClient = challonge.createClient({
apiKey: process.env.CHALLONGE_API_KEY,
})
const getPlayer = (playerXId) => {
return new Promise((resolve, reject) => {
challongeClient.participants.show({
id: tournamentURL,
participantId: playerXId,
callback: (err, data) => {
if (err) {
reject(err);
return;
}
peep = data.participant.misc
peepsList.push(peep)
console.log(peepsList)
console.log('RUNNING GET PLAYER', playerXId, playerIndexCount)
resolve(peepsList);
}
});
});
}
const peepsList = []
const matchList = []
const gotPeeps = () => {
challongeClient.tournaments.show({
id: tournamentURL,
include_participants: 1,
include_matches: 1,
callback: (err, data) => {
for (const [key, value] of Object.entries(data.tournament.matches)) {
if (value.match.state === 'open') {
peepsList.push(value.match.player1Id, value.match.player2Id)
console.log(peepsList)
}
}
}
/*// GET PLAYERS
getPlayer(value.match.player1Id)
.then(() => {
getPlayer(value.match.player2Id)
})
}
*/
}
)
}
gotPeeps()
}}
You can make this function return a promise and await the function (as long as your function is an async function)
const gotPeeps = () => {
return new Promise((resolve, reject) => {
const peepsList = []; // declare the empty array to e filled
challongeClient.tournaments.show({
id: tournamentURL,
callback: (err, data) => {
for (const [key, value] of Object.entries(data.tournament.matches)) {
if (value.match.state === "open") {
peepsList.push(value.match.player1Id, value.match.player2Id);
}
}
resolve(peepsList); // resolve the promise with the filled array
// TODO: handle reject
},
});
})
};
(async () => {
try {
const result = await gotPeeps();
} catch (error) {
// TODO: handle error
}
})();

How can I wait for the loop to Complete?

Let me first show you what the code looks like
Cart.getCompleteCart((cart)=>{
let products = []; *//this Array has to be filled*
totalPrice = cart.totalPrice;
for(let prod of cart.products){
Product.getProductDetails(prod.productId, productDetails => {
products.push({
product: productDetails,
productCount: prod.productCount
});
});
}
console.log("Products are ->", products); *//this line is running before for loop!*
}
console.log() is running before the for loop completes its work.
How to run "for loop" kind of in sync with the console.log() ?
I assume that Product.getProductDetails() is an async method (it looks like a query to an API).
console.log() is not "running before the for loop", it is just that the tasks performed by Product.getProductDetails() are being handled asynchronously.
Since it also seems that the Product.getProductDetails() method works with callbacks, one way to achieve your goal would be to promisify each call and then condensate all the promises into a single one by using Promise.all() method.
Something like this should do the trick:
Cart.getCompleteCart((cart) => {
const promises = [];
for (let prod of cart.products) {
promises.push(
new Promise((resolve) => {
Product.getProductDetails(prod.productId, (productDetails) => {
resolve({
product: productDetails,
productCount: prod.productCount
});
});
})
);
}
Promise.all(promises).then((products) => {
console.log('Products are ->', products);
});
});
Or:
Cart.getCompleteCart((cart) => {
Promise.all(
cart.products.map((prod) => {
return new Promise((resolve) => {
Product.getProductDetails(prod.productId, (productDetails) => {
resolve({
product: productDetails,
productCount: prod.productCount
});
});
});
})
).then((products) => {
console.log('Products are ->', products);
});
});
Promise.all is designed for pretty much this exact use-case:
// A dummy "Product" with a dummy "getProductDetails" implementation
// so that we can have a working test example
let Product = {
getProductDetails: (productId, callback) => {
setTimeout(() => {
callback({ type: 'productDetails', productId });
}, 100 + Math.floor(Math.random() * 200));
}
};
// This is the function you're looking for:
let getCompleteCart = async (cart) => {
return Promise.all(cart.products.map(async ({ productId, productCount }) => ({
product: await new Promise(resolve => Product.getProductDetails(productId, resolve)),
productCount
})));
}
let exampleCart = {
products: [
{ productId: 982, productCount: 1 },
{ productId: 237, productCount: 2 },
{ productId: 647, productCount: 5 }
]
};
getCompleteCart(exampleCart).then(console.log);
A breakdown of getCompleteCart:
let getCompleteCart = async (cart) => {
return Promise.all(cart.products.map(async ({ productId, productCount }) => ({
product: await new Promise(resolve => Product.getProductDetails(productId, resolve)),
productCount
})));
}
Promise.all (mdn) is purpose-built to wait for every promise in an Array of promises to resolve
We need to supply an Array of Promises to Promise.all. This means we need to convert our Array of data (cart.products) to an Array of Promises; Array.protoype.map is the perfect tool for this.
The function provided to cart.products.map converts every product in the cart to an Object that looks like { product: <product details>, productCount: <###> }.
The tricky thing here is getting the value for the "product" property, since this value is async (returned by a callback). The following line creates a promise that resolves to the value returned by Product.getProductDetails, using the Promise constructor (mdn):
new Promise(resolve => Product.getProductDetails(productId, resolve))
The await keyword allows us to convert this promise into the future value it results in. We can assign this future value to the "product" attribute, whereas the "productCount" attribute is simply copied from the original item in the cart:
{
product: await new Promise(resolve => Product.getProductDetails(productId, resolve)),
productCount
}
In order to run console.log at the right time we need to wait for all product details to finish compiling. Fortunately this is handled internally by getCompleteCart. We can console.log at the appropriate time by waiting for getCompleteCart to resolve.
There are two ways to do this:
Using Promise.prototype.then (mdn):
getCompleteCart(exampleCart).then(console.log);
Or, if we're in an async context we can use await:
let results = await getCompleteCart(exampleCart);
console.log(results);

loop synchronously through array while calling asynchronous function

My problem: i have an array with users and an user_hash and need to find the matching user. I came up with the following solution:
//results is the array user objects
//safety_first.getUserHash is a function that calculates a user_hash, it retrieves the user-record based on the id and then combines values of some fields and a key and turns this into a hash.
if (results.length > 0)
{
var i = 0;
function checkUserhash(user_id, user_hash, callback) {
safety_first.getUserHash(user_id, function(check_user_hash) {
if (user_hash == check_user_hash)
{
callback(user_id);
}
else
{
if ((i+1) < results.length)
{
i++;
checkUserhash(results[i].id, user_hash, callback);
}
else
{
callback(false);
}
}
});
}
checkUserhash(results[i].id, user_hash, function(user_id) {
if (user_id)
{
console.log("MATCH: "+user_id);
}
else
{
console.log("NO MATCH");
}
});
}
I first tried to do this in a for-loop but cause it calls the checkUserhash asychronously i could not break the loop when the match was found.
I'm looking for other possible solutions, please share your thoughts.
regards, Pieter
You can map over your user array to create an array of promises. Use Promise.all to wait for those promises to resolve and then iterate over the responses to check whether the hash matches the id.
In this example I've used async/await. I've also mocked up a hashing function routine so you can see it in action. Simply change the resolve from 'id' to 'id + 1' and rerun the demo to see the Match become No match.
Hope this is some use.
const safety_first = {
getUserHash(id) {
return new Promise(resolve => {
setTimeout(() => resolve(id), 1000);
});
}
}
async function checkUserHashes(userList, promises) {
try {
// await the promises to all resolve
// `Promise.all` preserves the order...
const res = await Promise.all(promises);
// ...which makes it useful if you need to check the returned
// results against the promise-producing array
userList.forEach(({ id }, i) => {
if (id === res[i]) console.log(`${id}|${res[i]} - Match`);
if (id !== res[i]) console.log(`${id}|${res[i]} - No match`);
});
} catch (e) {
console.log(e);
}
};
const userList = [{ id: 1, userHash: 1 }, { id: 2, userHash: 2 }];
// `map` over the fields and return a
// validation promise for each
const promises = userList.map(({ id }) => {
return safety_first.getUserHash(id);
});
// Pass in the original userList, and the generated promises
checkUserHashes(userList, promises);
Update: if you want to break out of the loop when a match has been found that's a little easier:
const safety_first = {
getUserHash(id) {
return new Promise(resolve => {
// This will match the second object (index 1)
setTimeout(() => resolve(id === 1 ? id: id + 1), 1000);
});
}
}
async function checkUserHashes(userList) {
// iterate over the array of objects
for (let [index, obj] of userList.entries()) {
// await for that one hash check
const res = await safety_first.getUserHash(obj.id);
// if it matches return the id
if (obj.id === res) return obj;
}
// otherwise return null if there are no matches
return null;
}
const userList = [{ id: 0, userHash: 1 }, { id: 1, userHash: 2 }];
(async () => {
const id = await checkUserHashes(userList);
console.log(id);
})();

Insert Multiple Items Into DynamoDB Using Promise.All

I'm trying to insert multiple items into a DynamoDB table using Promise.all() by reusing a function that inserts a single item and returns a promise. The problem I'm having is the array of returned data objects is empty. That data object is returned correctly when I insert a single item.
I based my implementation on this AWS Post.
My implementation is below:
function addMessage(message) {
const timestamp = Math.floor(new Date().getTime() / 1000);
const params = {
TableName: process.env.DYNAMODB_TABLE,
Item: {
esn: message.esn,
utcMsgTimestamp: parseInt(message.utcMsgTimestamp),
payload: message.payload,
createdAt: timestamp
}
};
console.log(params);
return dynamoDb.put(params).promise();
}
function addMessages(messages) {
// Make all of the addMessage calls immediately, this will return a rejected promise if any fail
return Promise.all(
messages.map(message => {
return addMessage(message);
})
);
}
My Jest unit test case is here:
it.only("add multiple messages", () => {
const dynamodb = require("../dynamodb");
expect.assertions(0);
const messages = [
{
esn: "0-700001",
utcMsgTimestamp: "1034268516",
payload: "0xC0560D72DA4AB2445A"
},
{
esn: "0-99991",
utcMsgTimestamp: "1034268521",
payload: "0xA14AA1DBDB818F9759"
}
];
return dynamodb.addMessages(messages).then(dataArray => {
dataArray.forEach(element => {
console.log(element);
});
// expect(true).toBeTruthy();
});
Any help would be greatly appreciated.

Unable to resolve promise chain

I am trying to write a Sequelize migration script win which I am trying to update my database but it is having many asynchronous operations (database queries and then updating database with particular id)
Here is my code
return db.organizationEntries
.findAll()
.then((entries) => {
return entries.forEach(entry => {
console.log(entry);
db.organizationEntries
.findAll({
attributes: [
[
db.sequelize.fn(
'MAX',
db.sequelize.col('organizationEntries.serial_number')
),
'maximum_serial_no'
]
],
where: {
organizationId: entry.organizationId
}
})
.then(orgEntry => {
console.log(orgEntry[0].dataValues.maximum_serial_no);
let data = { serialNumber: orgEntry[0].dataValues.maximum_serial_no + 1 };
console.log(data)
//problem
db.organizationEntries.update(data, {
where: {
id: entry.id
}
})
.then((result) => {
console.log(result);
})
});
// promises.push(promise);
});
// return Promise.all(promises);
})
Actually what I am trying to do is I am trying to take the list of all orgEntries from the database and then I am finding maximum serial number for that organization_id and then updating that particular orgEntry and like this all these operations in a loop
Now the problem is coming all the things are going in order but after finding max_serial_no it is not updating the database and I am not able to resolve what I should do to make that asynchronous call work in this order
I think you can solve this in two ways:
Simultaneously Promises
In a following code I removed forEach in favor of Promise.all() and map()
The map() method create (and return) a new array with the results of calling a provided function on every element in the calling array.
Example:
let numbers = [1, 2, 3]
let doubledNumbers = numbers.map(n => n * 2)
// doubledNumbers [2, 4, 6]
The Promise.all() method take an array of Promises as argument and returns a single Promise that will be resolved when all promises will be resolved or rejected if one promise failed
Example:
let promise1 = findUserById(5)
let promise2 = findUserFriends(5)
Promise.all([promise1, promise2])
.then(values => {
// values: [user object, list of user friends]
})
Result:
db.organizationEntries.findAll()
.then(entries => {
return Promise.all(entries.map(entry => {
console.log(entry)
return db.organizationEntries.findAll({
where: {
organizationId: entry.organizationId
},
attributes: [
[
db.sequelize.fn('MAX', db.sequelize.col('organizationEntries.serial_number')),
'maximum_serial_no'
]
]
})
.then(orgEntry => {
console.log(orgEntry[0].dataValues.maximum_serial_no)
let data = { serialNumber: orgEntry[0].dataValues.maximum_serial_no + 1 }
console.log(data)
return db.organizationEntries.update(data, { where: { id: entry.id } })
})
}))
})
.then(result => {
// result: Array of updated organizationEntries
console.log(result)
})
Step by step Promises with reduce() method
The reduce() method applies a function against an accumulator and each element in the array (from left to right) to reduce it to a single value. (from MDN web docs)
Example:
let items = [{ name: 'pencil', price: 2 }, { name: 'book', price: 10 }]
let total = items.reduce((total, item) => total += item.price, 0)
// total: 12
Result:
db.organizationEntries.findAll()
.then(entries => {
return entries.reduce((previousPromise, entry) => {
console.log(entry)
return previousPromise
.then(_ => {
return db.organizationEntries.findAll({
where: {
organizationId: entry.organizationId
},
attributes: [
[
db.sequelize.fn('MAX', db.sequelize.col('organizationEntries.serial_number')),
'maximum_serial_no'
]
]
})
})
.then(orgEntry => {
console.log(orgEntry[0].dataValues.maximum_serial_no)
let data = { serialNumber: orgEntry[0].dataValues.maximum_serial_no + 1 }
console.log(data)
return db.organizationEntries.update(data, { where: { id: entry.id } })
})
.then(updatedEntry => {
console.log(updatedEntry)
})
}, Promise.resolve())
})
.then(result => {
// result: Last updated organization entry
console.log('finished')
})

Categories