JavaScript PromiseAll allSettled does not catch the rejected - javascript

I have a sns lambda function that returns void (https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html). This event orderId and one message'status: success' are what I'm publishing. I check if the 'orderId' exists in my data database in the sns subscription lambda event. If it already exists, update the database; if it doesn't, console error it.
I created an integration test in which I transmit a random 'uuid' that isn't a valid 'orderId,' but it appears that my promise doesn't capture the'rejected'. It should show in console error failed to find order... I'm not sure where I'm going wrong. Also My promise syntax looks complicated, is there any neat way, I can do it. Thank you in advance 🙏🏽
This is sns event, which listen the publishing
interface PromiseFulfilledResult<T> {
status: "fulfilled" | "rejected";
value: T;
}
const parseOrdersFromSns = (event: SNSEvent) => {
try {
return event.Records.flatMap((r) => JSON.parse(r.Sns.Message))
} catch (error) {
console.error('New order from SNS failed at parsing orders', { event }, error)
return []
}
}
export const handlerFn = async (event: SNSEvent): Promise<void> => {
const orders = parseOrdersFromSns(event)
if (orders.length === 0) return
const existingOrdersPromiseResult = await Promise.allSettled(
orders.map(
async (o) => await findOrderStateNode(tagOrderStateId(o.orderId))
)
); // This returns of data if the order exsiit other it will return undefined
const existingOrders = existingOrdersPromiseResult // should returns arrays of data
.filter(({ status }) => status === "fulfilled")
.map(
(o) =>
(
o as PromiseFulfilledResult<
TaggedDatabaseDocument<
OrderStateNode,
TaggedOrderStateId,
TaggedOrderStateId
>
>
).value
);
const failedOrders = existingOrdersPromiseResult.filter( // should stop the opeartion if the data is exsit
({ status }) => status === "rejected"
);
failedOrders.forEach((failure) => {
console.error("failed to find order", { failure });
});
const updateOrder = await Promise.all(
existingOrders.map((o) => {
const existingOrderId = o?.pk as TaggedOrderStateId;
console.log({ existingOrderId }); // Return Undefined
})
);
return updateOrder;
};
this is my test suite
describe('Creating and updating order', () => {
integrationTest(
'Creating and updating the order',
async (correlationId: string) => {
CorrelationIds.set('x-correlation-id', correlationId)
const createdOrder = await createNewOrder(correlationId) // This create random order
if (!createdOrder.id) {
fail('order id is not defined')
}
const order = await getOrder(createdOrder.id)
// Add new order to table
await initializeOrderState([order])
const exisitingOrder = await findOrderStateNode(tagOrderStateId(order.id))
if (!exisitingOrder) fail(`Could not existing order with this orderId: ${order.id}`)
const event = {
Records: [
{
Sns: {
Message: JSON.stringify([
{
orderId: uuid(), // random order it
roundName,
startTime,
},
{
orderId: order.id,
roundName,
startTime,
},
{
orderId: uuid(),
roundName,
startTime,
},
]),
},
},
],
} as SNSEvent
await SnsLambda(event)
const updateOrderState = await findOrderStateNode(tagOrderStateId(order.id))
expect(updateOrderState?.status).toEqual('success')
},
)
})

Related

ref is stacking in sockets react native

Im trying to build chat application using sockets and everything is working except when im trying to add new session I beilieve it is stacking the reference but I don't know what I'm missing
here is my code
const [sessions, setSessions] = useState([]);
const userSocketRef = useRef(null);
useEffect(() => {
async function getUser() {
const user = await authStorage.getUser();
const URL = "ws://192.168.1.176:3001/" + "users";
if (userSocketRef.current === null) {
userSocketRef.current = io(URL, {
auth: { user: user.uuid },
transports: ["polling", "websocket"],
});
userSocketRef.current.on("disconnect", () => {
console.log("disconnected");
});
userSocketRef.current.on("connect", () => {
console.log("connected");
});
userSocketRef.current.onAny((event, ...args) => {
console.log("event");
});
userSocketRef.current.on("connect_error", (err) => {
console.log("connect_error");
});
}
}
console.log("current sessions after handle: "+Object.keys(sessions))
getUser();
if (userSocketRef.current !== null ) {
userSocketRef.current.on(
"private message",
(message, sessionUuid) => {
console.log("private message");
handleUpdateSession(message, sessionUuid);
}
);
userSocketRef.current.on("new session", async (session) => {
console.log(Object.keys(sessions));
console.log(Object.keys(session));
await handleNewSession(session);
});
}
}, [sessions]);
useEffect(() => {
async function getSessions() {
const user = await authStorage.getUser();
const ret = await getUserSessions(user?.uuid);
setSessions(ret.data.reverse());
}
getSessions();
}, []);
const handleNewSession = async (newSession) => {
console.log("current sessions: " + Object.keys(sessions));
console.log("new session: " + Object.keys(newSession));
setSessions([newSession, ...sessions]);
};
const handleUpdateSession = (message, sessionUuid) => {
try {
console.log(sessionUuid, Object.keys(sessions));
const temp = sessions;
const session = temp.find((s) => s.uuid === sessionUuid);
session.messages.push(message);
const filteredSessions = temp.filter((s) => s.uuid !== sessionUuid);
setSessions([session, ...filteredSessions]);
} catch (error) {
console.log(error);
}
};
now when i try to open new session it works great and I can send messages but when the user tries to send message it duplicates so many times and it gets errors because they are the same key I tried to debug and here is my conclusion
LOG current sessions after handle:
LOG current sessions after handle:
LOG connected
LOG event
LOG []
LOG ["uuid", "createdAt", "updatedAt", "expirationDate", "name", "device", "messages"]
LOG current sessions:
LOG new session: uuid,createdAt,updatedAt,expirationDate,name,device,messages
LOG current sessions after handle: 0
LOG event
LOG private message
LOG a0d35995-8d79-433a-aab9-1d911d20e756 []
LOG [TypeError: undefined is not an object (evaluating 'session.messages')]
LOG private message
LOG a0d35995-8d79-433a-aab9-1d911d20e756 ["0"]
LOG current sessions after handle: 0
as you can see the useEffect loads twice I don't know why then i trigger new session and then session set to the state then I try to send message from the sender you notice that the session.messages is undefined then it finds the session
I don't know why does it stack like this
thanks

Paypal buttons error: 'Expected order id to be passed'

I'm using #paypal/checkout-server-sdk and react-paypal-buttons-v2. I have almost everything set up, but I'm getting this error: {err: "Error: Expected an order id to be passed\n}
Here's what I have so far (PayPalButton):
<PayPalButton
createOrder={(data, actions) => {
return createPaypalOrder(cart, currentUser.uid, cartTotal())
.then(res => {
return res
})
.catch(err => {
console.log('paypal createOrder error')
})
}}
onApprove={(data, actions) => {
console.log("onApprove data", data, 'actions', actions)
}}
// shippingPreference="NO_SHIPPING" // default is "GET_FROM_FILE"
onSuccess={ (details, data) => {
// Clear cart
alert("Success: Clearing cart...")
dispatch({type: Actions.CLEAR_CART})
router.push('/account')
}}
/>
firebase function (firebase):
exports.createPaypalOrder = functions.https.onCall(async (data, ctx) => {
const apiEndpoint = "https://edit-elements.cdn.prismic.io/api/v2"
const Prismic = require('#prismicio/client')
const pClient = Prismic.client(apiEndpoint)
const productIDs = Object.keys(data.products)
const coupon = data.coupon.id ? await pClient.getByID(data.coupon.id).then(res => res).catch(err => new Error("Error verifying coupon")) : null
const products = await pClient.getByIDs(productIDs).then(res => res.results).catch(err => new Error("Error verifying products on server"))
// First we double-check pricing on server
const cartTotal = () => {
const total = Object.values(products).reduce((prev, curr) => {
const currPrice = (curr.data.on_sale && curr.data.sale_price) ? curr.data.sale_price : curr.data.price
return prev + currPrice
}, 0)
if (coupon.id) {
return (total - ( total * (coupon.data.percent_to_discount / 100) )).toFixed(2)
} else {
return total.toFixed(2)
}
}
const calcCoupon = (price, discount) => {
return (price - (price * (discount / 100)).toFixed(2))
}
// Assemble PayPal product items array
let itemList = []
Object.entries(products).map(([key, prod]) => {
itemList.push({
name: prod.data.title[0].text,
description: prod.data.short_description[0].text,
sku: prod.id,
unit_amount: {
currency_code: 'USD',
value: prod.data.on_sale && prod.data.sale_price ? prod.data.sale_price : prod.data.price,
on_sale: Boolean(prod.data.on_sale),
price_with_coupon: coupon.data.percent_to_discount ? calcCoupon((prod.data.on_sale && prod.data.sale_price ? prod.data.sale_price : prod.data.price), coupon.data.percent_to_discount).toFixed(2) : 'no coupon used'
},
quantity: 1,
category: prod.data.category.uid
})
})
// if (coupon.id) itemList.push({coupon_used: coupon.uid})
// Then we create PayPal order
const checkoutNodeSDK = require('#paypal/checkout-server-sdk')
const env = () => {
const clientId = functions.config().paypal_sandbox.client_id
const clientSecret = functions.config().paypal_sandbox_secret
return new checkoutNodeSDK.core.SandboxEnvironment(clientId, clientSecret)
}
const client = () => {
return new checkoutNodeSDK.core.PayPalHttpClient(env)
}
// The request for PayPal
const request = new checkoutNodeSDK.orders.OrdersCreateRequest()
request.prefer('return=representation')
request.requestBody({
intent: 'CAPTURE',
purchase_units: [{
amount: {
currency_code: 'USD',
value: cartTotal()
},
items: itemList,
coupon_used: coupon.uid ? coupon.uid : 'no coupon used'
}]
})
let order
try {
order = await client().execute(request)
return order.result.id
} catch(err) {
// We handle the paypal error object
return new Error("Error with paypal: ", err)
}
})
I'm just not sure what the flow is with these 2 components, and the docs are just vague enough on each to not give me enough to intuitively figure out how to route this. My guess is:
PayPal Button createOrder -(cart/uid_auth)-> custom server validation -(return orderID)-> PayPal Button onApprove/onSuccess -> SUCCESS
Please let me know if something is unclear, or if I've made a mistake. Thanks!

Array.map() doesn't render anything in React

I'm trying to make a list in my react app. I have retrieved data from my database, and pushed it into a list. I have doublechecked that the data shows up correctly in the console, and it does, but array.map() returns nothing. I think the problem might be that array.map() runs two times. I don't know why it runs two times.
function Dashboard() {
const user = firebase.auth().currentUser;
const [teams, setTeams] = useState([])
const history = useHistory();
useEffect(() => {
getTeams()
if (user) {
} else {
history.push("/")
}
}, [])
function Welcome() {
if (user) {
return <h1>Welcome, {user.displayName}</h1>
} else {
}
}
const getTeams = () => {
firebase.firestore().collectionGroup('members').where('user', '==', user.uid).get().then((snapshot) => {
const docList = []
snapshot.forEach((doc) => {
docList.push({
teamId: doc.data().teamId,
})
})
const teamslist = []
docList.forEach((data) => {
firebase.firestore().collection('teams').doc(data.teamId).get().then((doc) => {
teamslist.push({
name: doc.data().name,
teamId: doc.id,
})
})
})
setTeams(teamslist)
})
}
const openTeam = (data) => {
console.log(data.teamId)
}
return (
<div>
<Welcome />
<div>
<ul>
{console.log(teams)}
{teams.map((data) => {
return (
<li onClick={() => openTeam(data)} key={data.teamId}>
<h1>{data.name}</h1>
<p>{data.teamId}</p>
</li>
)
})}
</ul>
</div>
</div>
)
}
export default Dashboard
The getTeams function has a bug where it isn't waiting for the firebase.firestore().collection('teams').doc(data.teamId).get().then promises to finish before calling setTeams, so it is called with an empty array, causing React to trigger a render with the empty array.
As the promises for fetching each team resolve they will be pushed to the same array reference, but this won't trigger a rerender in React since you're not calling setTeams again when the array changes.
Try this code, which won't call setTeams until each team promise generated from docList has been resolved.
const getTeams = () => {
firebase.firestore().collectionGroup('members').where('user', '==', user.uid).get().then((snapshot) => {
const docList = []
snapshot.forEach((doc) => {
docList.push({
teamId: doc.data().teamId,
})
})
const teamslist = [];
Promise.all(docList.map((data) => {
return firebase
.firestore()
.collection('teams')
.doc(data.teamId)
.get()
.then((doc) => {
teamslist.push({
name: doc.data().name,
teamId: doc.id,
})
})
}))
.then(() => setTeams(teamslist));
})
}
A smaller edit would be to call setTeams after each separate team promise resolves, which will trigger a React render each time a new team is resolved:
.then((doc) => {
teamslist.push({
name: doc.data().name,
teamId: doc.id,
});
// create a new array, since using the same array
// reference won't cause react to rerender
setTeams([...teamslist]);
})
Many thanks to #martinstark who provided you an answer while I was unavailable.
However, there are some more things that need to be covered.
User State
In your current component, you pull the current user from Firebase Authentication, but don't handle the state changes of that user - signing in, signing out, switching user. If a user is signed in and they were to navigate directly to your dashboard, firebase.auth().currentUser could be momentarily null while it resolves the user's login state, which would incorrectly send them off to your login page.
This can be added using:
const [user, setUser] = useState(() => firebase.auth().currentUser || undefined);
const userLoading = user === undefined;
useEffect(() => firebase.auth().onAuthStateChanged(setUser), []);
Next, in your first useEffect call, you call getTeams() whether the user is signed in or not - but it should depend on the current user.
useEffect(() => {
if (userLoading) {
return; // do nothing (yet)
} else if (user === null) {
history.push("/");
return;
}
getTeams()
.catch(setError);
}, [user]);
// This getTeams() is a () => Promise<void>
const getTeams = async () => {
const membersQuerySnapshot = await firebase.firestore()
.collectionGroup('members')
.where('user', '==', user.uid)
.get();
const docList = []
membersQuerySnapshot.forEach((doc) => {
docList.push({
teamId: doc.get("teamId"), // better perfomance than `doc.data().teamId`
});
});
const teamDataList = [];
await Promise.all(docList.map((data) => {
return firebase.firestore()
.collection('teams')
.doc(data.teamId)
.get()
.then(doc => teamDataList.push({
name: doc.get("name"),
teamId: doc.id
}));
}));
setTeams(teamDataList);
}
Optimizing getTeams() - Network Calls
The getTeams function in your question calls setTeams with the array [], which will be empty at the time of calling it as covered in #martinstark's answer. The "get team data" operations are asyncronous and you aren't waiting for them to resolve before updating your state and triggering a new render. While you are pushing data to them after the component has rendered, modifying the array won't trigger a new render.
While you could fetch the data for each team using db.collection("teams").doc(teamId).get(), each of these is requests is a network call, and you can only make a limited number of these in parallel. So instead of fetching 1 team per network call, you could fetch up to 10 teams per network call instead using the in operator and FieldPath.documentId().
Assuming the collectionGroup("members") targets the collections of documents at /teams/{aTeamId}/members which contain (at least):
"/teams/{aTeamId}/members/{memberUserId}": {
teamId: aTeamId,
user: memberUserId, // if storing an ID here, call it "uid" or "userId" instead
/* ... */
}
// this utility function lives outside of your component near the top/bottom of the file
function chunkArr(arr, n) {
if (n <= 0) throw new Error("n must be greater than 0");
return Array
.from({length: Math.ceil(arr.length/n)})
.map((_, i) => arr.slice(n*i, n*(i+1)))
}
// This getTeams() is a () => Promise<void>
const getTeams = async () => {
const membersQuerySnapshot = await firebase.firestore()
.collectionGroup('members')
.where('user', '==', user.uid)
.get();
const teamIDList = []
membersQuerySnapshot.forEach((doc) => {
teamIDList.push(doc.get("teamId")); // better perfomance than `doc.data().teamId`
})
const chunkedTeamIDList = chunkArr(teamIDList, 10) // split into batches of 10
const teamsColRef = firebase.firestore().collection('teams');
const documentId = firebase.firestore.FieldPath.documentId(); // used with where() to target the document's ID
const foundTeamDocList = await Promise
.all(chunkedTeamIDList.map((chunkOfTeamIDs) => {
// fetch each batch of IDs
return teamsColRef
.where(documentId, 'in', chunkOfTeamIDs)
.get();
}))
.then((arrayOfQuerySnapshots) => {
// flatten results into a single array
const allDocsList = [];
arrayOfQuerySnapshots.forEach(qs => allDocsList.push(...qs.docs));
return allDocsList;
});
const teamDataList = foundTeamDocList
.map((doc) => ({ name: doc.get("name"), teamId: doc.id }));
// sort by name, then by ID
teamDataList.sort((aTeam, bTeam) =>
aTeam.name.localeCompare(bTeam.name) || aTeam.teamId.localeCompare(bTeam.teamId)
)
// update state & trigger render
setTeams(teamDataList);
}
You can also make use of this utility function to simplify & optimize the code a bit. Which gives:
// This getTeams() is a () => Promise<void>
const getTeams = async () => {
const membersQuerySnapshot = await firebase.firestore()
.collectionGroup('members')
.where('user', '==', user.uid)
.get();
const teamIDList = []
membersQuerySnapshot.forEach((doc) => {
teamIDList.push(doc.get("teamId")); // better perfomance than `doc.data().teamId`
})
const teamsColRef = firebase.firestore().collection('teams');
const teamDataList = [];
await fetchDocumentsWithId(
teamsColRef,
teamIDList,
(doc) => teamDataList.push({ name: doc.get("name"), teamId: doc.id })
);
// sort by name, then by ID
teamDataList.sort((aTeam, bTeam) =>
aTeam.name.localeCompare(bTeam.name) || aTeam.teamId.localeCompare(bTeam.teamId)
)
// update state & trigger render
setTeams(teamDataList);
}
Optimizing getTeams() - Function Definition
As part of the last optimization, you could pull it out of your component or place it in its own file so that it's not redefined with every render:
// define at top/bottom of the file outside your component
// This getTeams() is a (userId: string) => Promise<{ name: string, teamId: string}[]>
async function getTeams(userId) => {
const membersQuerySnapshot = await firebase.firestore()
.collectionGroup('members')
.where('user', '==', userId)
.get();
const teamIDList = []
membersQuerySnapshot.forEach((doc) => {
teamIDList.push(doc.get("teamId")); // better perfomance than `doc.data().teamId`
})
const teamsColRef = firebase.firestore().collection('teams');
const teamDataList = [];
await fetchDocumentsWithId(
teamsColRef,
teamIDList,
(doc) => teamDataList.push({ name: doc.get("name"), teamId: doc.id })
);
// sort by name, then by ID
teamDataList.sort((aTeam, bTeam) =>
aTeam.name.localeCompare(bTeam.name) || aTeam.teamId.localeCompare(bTeam.teamId)
)
// return the sorted teams
return teamDataList
}
and update how you use it:
useEffect(() => {
if (userLoading) {
return; // do nothing
} else if (user === null) {
history.push("/");
return;
}
getTeams(user.uid)
.then(setTeams)
.catch(setError);
}, [user]);

Unhandled Rejection outside Promise?

So basically I am running some code that queries a graphql api, and checks whether the data field inside is null (meaning there are no hits for the query), like so:
// check if the items have available data.
// if not, throw an error message saying which ones did not have data.
const testIfDataInDB = await Promise.all(objIDs.map(objID => {
return fetch("/api/graphql", {
method: "POST",
headers: headers,
body: JSON.stringify({
query: "query get_object_with_obj_number($objectNumber: String!) { constructionDetail(objectNumber: $objectNumber) { objectNumber } }",
variables: {
objectNumber: objID
}
}
)
}).then(async (res) => {
return {
responseData: await res.json(),
objID: objID
}
}
)
}));
const itemsDataNotFound = testIfDataInDB.filter(o =>
o.responseData?.data?.constructionDetail === null);
let errString = "";
if (itemsDataNotFound.length > 0) {
itemsDataNotFound.forEach(obj => {
errString = errString + `${obj.objID}, `;
});
errString = errString.slice(0, errString.length - 2);
console.log(errString);
throw new Error(errString);
}
// ... do some object transformations ...
// proceed to post some items into another db using fetch.
const response = await Promise.all(objectsArr.map((obj, index) => {
const body = JSON.stringify(obj);
if (update && index === 0) {
const fetchUrl = `${baseFetchUrl}/${parentItemMetadata._id}`
return fetch(
fetchUrl, {
method: "PUT",
headers: headers,
body: body
}
).then(res => res.json())
}
return fetch(
baseFetchUrl, {
method: "POST",
headers: headers,
body: body
}
).then(res => res.json())
}))
console.log(response);
return response;
}
full code for this part
and the way im calling this code inside my react component:
//defined in parent
const saveMultipleItems = async (
objIDs: string[],
update: boolean
) => {
const parentItemMetaData: TypeLabelItemMetaData = {
createdBy: currentItem?.createdBy || "",
createdAt: currentItem?.createdAt || "",
updatedBy: currentItem?.updatedBy || "",
updatedAt: currentItem?.updatedAt || "",
_id: currentItem?._id || "",
collection: currentItem?.collection || ""
}
try {
saveMultiple(
objIDs,
labelType,
values as TypeLabelState,
parentItemMetaData,
user,
update
)
} catch (e) {
console.warn(e.message);
} finally {
fetchItems();
}
// called in child component
onSubmit={(e) => {
e.preventDefault();
console.log("submitting a series")
try {
const pNumbersArray = generateArray(fromObjNumber, toObjNumber);
saveSeriesFn(pNumbersArray, editMode);
} catch (e) {
console.log("catched error")
alert("Saving series failed with message " + e.message);
}
}
}
My plan was to throw an error, and react to this error somewhere where I can show an alert saying that some items are not available to be saved, and abort the saving operation. I kn
I know this may not be the cleanest way to do this (I am also very grateful for sugestions). The problem I am having right now, is that the app crashes with the following:
Unhandled Rejection (Error): P18100405117, P18100405118, P18100405119
saveMultiple
src/app/src/api/savemultiple.ts:70
67 | });
68 | errString = errString.slice(0, errString.length - 2);
69 | console.log(errString);
70 | throw new Error(errString);
| ^ 71 | }
72 |
73 | const objectsArr: SaveTemplateObjType[] = objIDs.map((objID, index) => {
I thought I was not inside a Promise at the moment I threw this Error?
Async/Await is a synthetic sugar for Promises
Your code states: const saveMultiple = async(...
An Async function will always return a Promise. (see refs below)
Your try/catch doesnt take affect in:
try {
saveMultiple(
objIDs,
labelType,
values as TypeLabelState,
parentItemMetaData,
user,
update
)
} catch (e) {
console.warn(e.message);
} finally {
fetchItems();
}
because saveMultiple is async. you need to use:
saveMultiple(
objIDs,
labelType,
values as TypeLabelState,
parentItemMetaData,
user,
update
).catch((e) => {
console.warn(e.message);
}).finally(() => {
fetchItems();
})
From MDN:

Counter not increasing in async map function

I am working with mongodb and nodejs. I have an array of customers I have to create each inside database.
const promises2 = customers.map(async customer => {
if (!customer.customerId) {
const counter = await Counter.findOne({ type: "Customer" });
console.log({counter});
const payload = {
customerId: counter.sequence_value,
};
await Customer.create(payload);
await Counter.findOneAndUpdate({ type: "Customer" }, { $inc: { sequence_value: 1 } });
}
});
await Promise.all([...promises2]);
The issue is counter is not increasing every time. I am getting same counter in all the created customers. What is the issue here?
Issue is something like this but don't have an answer.
The problem is that all the calls overlap. Since the first thing they each do is get the current counter, they all get the same counter, then try to use it. Fundamentally, you don't want to do this:
const counter = await Counter.findOne({ type: "Customer" });
// ...
await Counter.findOneAndUpdate({ type: "Customer" }, { $inc: { sequence_value: 1 } });
...because it creates a race condition: overlapping asynchronous operations can both get the same sequence value and then both issue an update to it.
You want an atomic operation for incrementing and retrieving a new ID. I don't use MongoDB, but I think the findOneAndUpdate operation can do that for you if you add the returnNewDocument option. If so, the minimal change would be to swap over to using that:
const promises2 = customers.map(async customer => {
if (!customer.customerId) {
const counter = await Counter.findOneAndUpdate(
{ type: "Customer" },
{ $inc: { sequence_value: 1 } },
{ returnNewDocument: true }
);
console.log({counter});
const payload = {
customerId: counter.sequence_value,
};
await Customer.create(payload);
}
});
await Promise.all([...promises2]);
...but there's no reason to create an array and then immediately copy it, just use it directly:
await Promise.all(customers.map(async customer => {
if (!customer.customerId) {
const counter = await Counter.findOneAndUpdate(
{ type: "Customer" },
{ $inc: { sequence_value: 1 } },
{ returnNewDocument: true }
);
console.log({counter});
const payload = {
customerId: counter.sequence_value,
};
await Customer.create(payload);
}
}));
The overall operation will fail if anything fails, and only the first failure is reported back to your code (the other operations then continue and succeed or fail as the case may be). If you want to know everything that happened (which is probably useful in this case), you can use allSettled instead of all:
// Gets an array of {status, value/reason} objects
const results = await Promise.allSettled(customers.map(async customer => {
if (!customer.customerId) {
const counter = await Counter.findOneAndUpdate(
{ type: "Customer" },
{ $inc: { sequence_value: 1 } },
{ returnNewDocument: true }
);
console.log({counter});
const payload = {
customerId: counter.sequence_value,
};
await Customer.create(payload);
}
}));
const errors = results.filter(({status}) => status === "rejected").map(({reason}) => reason);
if (errors.length) {
// Handle/report errors here
}
Promise.allSettled is new in ES2021, but easily polyfilled if needed.
If I'm mistaken about the above use of findOneAndUpdate in some way, I'm sure MongoDB gives you a way to get those IDs without a race condition. But in the worst case, you can pre-allocate the IDs instead, something like this:
// Allocate IDs (in series)
const ids = [];
for (const customer of customers) {
if (!customer.customerId) {
const counter = await Counter.findOne({ type: "Customer" });
await Counter.findOneAndUpdate({ type: "Customer" }, { $inc: { sequence_value: 1 } });
ids.push(counter.sequence_value);
}
}
// Create customers (in parallel)
const results = await Promise.allSettled(customers.map(async(customer, index) => {
const customerId = ids[index];
try {
await Customer.create({
customerId
});
} catch (e) {
// Failed, remove the counter, but without allowing any error doing so to
// shadow the error we're already handling
try {
await Counter.someDeleteMethodHere(/*...customerId...*/);
} catch (e2) {
// ...perhaps report `e2` here, but don't shadow `e`
}
throw e;
}
});
// Get just the errors
const errors = results.filter(({status}) => status === "rejected").map(({reason}) => reason);
if (errors.length) {
// Handle/report errors here
}
Your map function is not returning a promise.
Try this :
const promises2 = [];
customers.map((customer) => {
return new Promise(async (resolve) => {
if (!customer.customerId) {
const counter = await Counter.findOne({ type: 'Customer' });
console.log({ counter });
const payload = {
customerId: counter.sequence_value,
};
await Customer.create(payload);
await Counter.findOneAndUpdate({ type: 'Customer' }, { $inc: { sequence_value: 1 } });
}
resolve();
});
});
await Promise.all(promises2);

Categories

Resources