I am trying to fetch a record from a database. Due to race conditions it is possible and even likely that the record isn't there when I first try to fetch it. How do I wrap this in a retry logic without going mad? I seem to be too stupid for it
const booking = await strapi.query("api::booking.booking").findOne({
where: {
id: id,
},
});
This code should retry n
times with a delay of t
milliseconds. Thanks and much love.
What I've tried:
async function tryFetchBooking(
id,
max_retries = 3,
current_try = 0,
promise
) {
promise = promise || new Promise();
// try doing the important thing
const booking = await strapi.query("api::booking.booking").findOne({
where: {
id: id,
},
});
if (!booking) {
if (current_try < max_retries) {
console.log("No booking. Retrying");
setTimeout(function () {
tryFetchBooking(id, max_retries, current_try + 1, promise);
}, 500);
} else {
console.log("No booking. Giving up.");
promise.reject(new Error("no booking found in time"));
}
promise.catch(() => {
throw new Error(`Failed retrying 3 times`);
});
} else {
console.log("Found booking with retry");
promise.resolve(booking);
}
}
const booking = await tryFetchBooking(id);
The thrown error:
This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason:
TypeError: Promise resolver undefined is not a function
I am trying to fetch a record from a database. Due to race conditions it is possible and even likely that the record isn't there when I first try to fetch it. How do I wrap this in a retry logic without going mad? I seem to be too stupid for it
const booking = await strapi.query("api::booking.booking").findOne({
where: {
id: id,
},
});
This code should retry n
times with a delay of t
milliseconds. Thanks and much love.
What I've tried:
async function tryFetchBooking(
id,
max_retries = 3,
current_try = 0,
promise
) {
promise = promise || new Promise();
// try doing the important thing
const booking = await strapi.query("api::booking.booking").findOne({
where: {
id: id,
},
});
if (!booking) {
if (current_try < max_retries) {
console.log("No booking. Retrying");
setTimeout(function () {
tryFetchBooking(id, max_retries, current_try + 1, promise);
}, 500);
} else {
console.log("No booking. Giving up.");
promise.reject(new Error("no booking found in time"));
}
promise.catch(() => {
throw new Error(`Failed retrying 3 times`);
});
} else {
console.log("Found booking with retry");
promise.resolve(booking);
}
}
const booking = await tryFetchBooking(id);
The thrown error:
This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason:
TypeError: Promise resolver undefined is not a function
Share
Improve this question
asked Jul 21, 2022 at 21:54
Ole SpaarmannOle Spaarmann
16.8k28 gold badges106 silver badges167 bronze badges
3
- Something like that? stackoverflow./a/44577075/5767872 – Sergey Sosunov Commented Jul 21, 2022 at 21:58
- @SergeySosunov could you give me an example how to use it in my case? I'm struggling with the phrase "This is how you call it, assuming that func sometimes succeeds and sometimes fails, always returning a string that we can log:" So how does my function of fetching a booking would fail? It either returns a record or nothing. – Ole Spaarmann Commented Jul 21, 2022 at 22:11
- This can do it well - generic async retry function. – vitaly-t Commented Aug 11, 2024 at 17:14
3 Answers
Reset to default 6That promise.reject()
/promise.resolve()
approach is not going to work, you cannot resolve a promise from the outside. And you shouldn't need to - just return
/throw
from your async
function! The only place where you need to construct a new Promise
is in a little helper function
function delay(t) {
return new Promise(resolve => {
setTimeout(resolve, t);
});
}
Then you can write your function in a recursive manner:
async function tryFetchBooking(
id,
max_retries = 3,
current_try = 0,
) {
let booking = await strapi.query("api::booking.booking").findOne({
where: {
id: id,
},
});
if (!booking) {
if (current_try < max_retries) {
console.log("No booking. Retrying");
await delay(500);
// ^^^^^^^^^^^^^^^^
booking = await tryFetchBooking(id, max_retries, current_try + 1);
// ^^^^^^^^^^^^^^^^^^^^^
console.log("Found booking with retry");
} else {
console.log("No booking. Giving up.");
throw new Error("no booking found in time");
// or if you prefer the other error message:
throw new Error(`Failed retrying 3 times`);
}
}
return booking;
}
or even in an iterative manner:
async function tryFetchBooking(id, maxRetries = 3) {
let currentTry = 0;
while (true) {
const booking = await strapi.query("api::booking.booking").findOne({
where: {
id: id,
},
});
if (booking) {
return booking;
}
if (currentTry < maxRetries) {
await delay(500);
currentTry++;
} else {
console.log("No booking. Giving up.");
throw new Error("no booking found in time");
}
}
}
to avoid using await
inside a loop, you can use recursive function to call tryFetchBooking
async function tryFetchBooking(id, currentRetry = 0) {
const maxRetries = 3;
const booking = await strapi.query("api::booking.booking").findOne({
where: {
id: id,
},
});
if (booking) {
return booking;
}
if (currentRetry < maxRetries) {
await delay(500);
currentTry++;
return tryFetchBooking(id, currentRety);
} else {
console.log("No booking. Giving up.");
throw new Error("no booking found in time");
}
}
import { strapiMock } from "./mock";
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
// Credits to @Bergi
const retryOperation = (operation, delay, retries) =>
operation().catch((reason) =>
retries > 0
? wait(delay).then(() => retryOperation(operation, delay, retries - 1))
: Promise.reject(reason)
);
const throwIfNoResult = (result) => {
if (!result) throw new Error("No result");
return result;
};
const fetchBooking = (id) => {
/*
return strapi.query("api::booking.booking").findOne({
where: {
id: id
}
});
*/
return strapiMock(id);
};
async function tryFetchBooking(id, delay = 1000, retries = 4) {
const operation = () => fetchBooking(id).then(throwIfNoResult);
const wrapped = retryOperation(operation, delay, retries);
return await wrapped;
}
tryFetchBooking(1).then(console.log).catch(console.error);
Mock used:
let cnt = 0;
export const strapiMock = (id) => {
return new Promise((resolve, reject) => {
if (cnt++ === 3) {
cnt = 0;
// resolve(null);
resolve(id);
} else {
reject("no data");
}
});
};