简体   繁体   中英

timer wrapper function for recursive async/await call

I have a recursive async function getResponse(url,attempts = 0) , which polls external api for the response and resolves or exits after reaching X number of retries or on a server error. However, it's internal "clock" is based off the number of retries (after allowing for delays to avoid rate limits), but I also want to have a flexibility in setting a time based timer, which would resolve the function and end the recursion. Ideally, I want to be able to wrap time based timer around my recursive async function, like so timed(getResponse(url),3400)

I have only managed to have both the time based and "retries" based timer working together, by packaging both timers in one async function with local variable expired serving as an exit flag and setting Promise.race conditions on both functions.

async function timedgetResponse (expiry = 3500,url) {
  let expired = false;
  async function timeout(expiry){
    await new Promise(_=> setTimeout(_,expiry));
    expired = true;
    return false;
  };

async function getResponse(url,attempts = 0){
  try {
    if(expired){ return false; };
    const limit = 10;
    if(attempts >= limit){ok: false, e:"MAX_ATTEMPTS"};
    const rawRes = await fetch(url,
      {
        method: 'GET',
        credentials: 'include', 
        headers: {
          'Accept': 'application/json'
        }
      });
    if (!rawRes.ok) {  throw (Error('SERVER_ERROR')); }; 
    const res = await rawRes.json(); 
    if(!res || res.status === 0){ throw (Error(res.request)); };
    return {ok: true, res: res.request};
  } catch(e){
  const err = e.message; 
  if(err === "RESPONSE_NOT_READY"){
    await new Promise(_ => setTimeout(_, 333));
    attempts +=1;   
    return getResponse(url,attempts);
  } else 
  if(err === "SERVER_ERROR_ON_RESOLVER"){
    await new Promise(_ => setTimeout(_, 10000));
    attempts +=1;       
    return getResponse(url,attempts);
  } else {
    return {ok: false, e:"MISC_ERROR"};
  };

};    
};

  const awaited = await Promise.race([
    getResponse(url),
    timeout(expiry)
  ]);

return awaited;
};

I sense that it is not a correct way to do it and would appreciate any help towards timed(getResponse(url),3400) solution.

I have a function that might meet your need. I have updated it based on how I have interpreted your needs. The idea is you will poll until something is true ie resolves or you exceed the max attempts. It has a built-in configurable delay.

The idea here is you'd pass in a function that wraps your fetch call which would eventually resolve/reject.

setPolling(pollFunc, freq = 1000, maxAttempts = 3)

pollFunc = function that takes no args and returns a promise that eventually resolves or rejects.

freq = how frequently to run pollFunc in milliseconds

maxAttempts = max attempts before giving up

 const setPolling = async (pollFunc, freq = 1000, maxAttempts = 3, _attempts = 1) => { const wait = (delay) => new Promise(resolve=>setTimeout(resolve, delay)) try { return await pollFunc() } catch (e) { if (_attempts < maxAttempts) { await wait(freq) return await setPolling(pollFunc, freq, maxAttempts, ++_attempts) } throw (e instanceof Error) ? e : new Error((typeof e !== 'undefined') ? e : 'setPolling maxAttempts exceeded!') } } async function alwaysFail() { throw new Error(`alwaysFail, failed because that's what it does!`) } function passAfter(x) { let i = 0 return async ()=> { if (i > x) return `passAfter succeeded because i(${i}) > x(${x})` throw new Error(`i(${i++}) < x(${x})`) } } setPolling(alwaysFail) .catch((e)=>console.error(`alwaysFail, failed!\\n${e.message}\\n${e.stack}`)) setPolling(passAfter(5), 500, 10) .then((res)=>console.log(`passAfter, succeeded!\\n${res}`)) .catch((e)=>console.error(`passAfter, failed!\\n${e.message}\\n${e.stack}`)) 

On the basis that you want to stop retrying when a timer expires, then you can employ a token to convey a stop signal to the recursive process.

Something like this should do it:

const poll = async (work, options, token) => {
    const settings = Object.assign({ 'delay':0, 'attempts':1, 'maxAttempts':3 }, options);

    // Utility function which returns a Promise-wrapped setTimeout
    const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));

    // Two mechanisms for stopping the recursion.
    // Performing the tests here ensures they are applied before the first work() call.
    // 1. token-borne stop signal 
    if(token.stop) {
        throw new Error('poll(): stopped');
    }
    // 2. max attempts reached
    if (settings.attempts >= settings.maxAttempts) {
        throw new Error('poll(): max attempts reached');
    }

    // Do the work, and recurse on error
    try {
        return await work();
    }
    catch (e) {
        await delay(settings.delay);
        settings.attempts += 1; // Mutate/pass `settings`; the original `options` is not guaranteed to have an `attempts` property.
        return await poll(work, settings, token);
    }
}

Call as follows:

// token
const token = {}; // or {'stop':false} if you like

// Time based timer:
setTimeout(() => {
    token.stop = true; // raise the 'stop' flag
}, 60000); // or whatever

let pollPromise = poll(doSomethingAsync, {'delay':1000, 'maxAttempts':100}, token)
.then((res) => console.log(res))
.catch((e) => console.error(e));

Note that on setting the stop signal:

  1. a successful reponse from in-flight work will still come through.
  2. further recursion will be prevented but no attempt is made to abort the in-flight work.

With a little more thought these behaviours could be changed depending on exactly what is required.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM