Taking an object literal used as a wrapper around an API service.

const service = {
  find: async (filter) => {/*...*/},
  getEntry: async (id) => {/*...*/},
  updateEntry: async (id, update) => {/*...*/},
}

With an API service that returns a limit response such as Promise.reject(new Error('Quota limit exceeded')).

One approach for a retry mechanism is to use a Proxy handler for each method.

let count = 0;
const handler = {
  apply: async (target, thisArg, argumentsList) => {
    try {
      return await service[target.name](...argumentsList);
    } catch (e) {
      if (e.message === "Quota limit exceeded" && count < 50) {
        // random wait time between 300ms - 2300ms
        await new Promise((resolve) =>
          setTimeout(resolve, Math.floor(Math.random() * 2000 + 300))
        );
        count++;

        return await serviceWithProxy[target.name](...argumentsList);
      }
      throw new Error(e);
    }
  },
};

And to apply the handler.

const serviceWithProxy = { ...service };

Object.keys(serviceWithProxy).forEach((key) => {
  if (typeof serviceWithProxy[key] === "function") {
    serviceWithProxy[key] = new Proxy(serviceWithProxy[key], handler);
  }
});

module.exports = { default: serviceWithProxy, serviceWithoutProxy: service };

The count variable could be used as part of the wait time calculation to switch to an exponential backoff implementation.