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
CodePudding user response:
That 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");
}
}
}
CodePudding user response:
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");
}
});
};