轮询直到获得特定结果?

10

我目前正尝试使用这个链接https://davidwalsh.name/javascript-polling(以及其他很多链接)为我的应用程序添加轮询。

我已经可以访问以下已实现的API:

client.get('url')
// returns Promise with result of getting result from url
// for the application I am working on,
//     the URL returns json that looks like the following 
//     {status: DONE or IN PROGRESS, other values...}
// when status is DONE other values are what I will use in the application

client.post('url', {data: passAnyDataHere}) 
// sends a post request with result of sending data to url
// starts the specific job

我遇到的一个问题是,在尝试调整我上面链接的JavaScript轮询代码时,当我发现状态为DONE时,我没有办法将结果返回到Promise之外。 有人能给我一些如何做到这一点的提示吗?(不断轮询直到找到特定值,然后返回该值以供稍后使用)

让我举个例子

export default function someFunction() {
    let a = client.get('/status');
    a.then( dataResult => 
      {
         if (dataResult.status == "DONE") {
            //** want to get other values in dataResult here 
            // and store it somewhere else for use later
         }
      });
    // ***want to work with results here.
    // need some way to get the status of what happened inside the .then(..) part
   //  eventually have to return success or failure and results to the frontend
   // (this part is already done)
 }

代码的基础是https://github.com/erikras/react-redux-universal-hot-example#server-side-data-fetching(使用React.js / Node.js / Redux等)。

欢迎提供任何提示/建议/帮助。谢谢!

此外,我正在开发的应用程序不使用JQuery。

8个回答

27

这是一个更具扩展性的解决方案,基于这篇文章:使用 async/await 进行轮询

只需添加以下实用方法:

const poll = async function (fn, fnCondition, ms) {
  let result = await fn();
  while (fnCondition(result)) {
    await wait(ms);
    result = await fn();
  }
  return result;
};

const wait = function (ms = 1000) {
  return new Promise(resolve => {
    setTimeout(resolve, ms);
  });
};

那么你可以这样调用:

let fetchReport = () => axios.get(reportUrl);
let validate = result => !result.data.summary;
let response = await poll(fetchReport, validate, 3000);

2
最干净的解决方案!谢谢 - being_j
这是否意味着如果 fn 花费很长时间才能完成,那么下一次轮询就要等到 fn 返回后才会发生,对吗?例如,假设 fetchReport 花费6秒钟才能完成,而 ms 是3000(或3秒)。这意味着API调用需要6秒钟>等待另外3秒钟才能进行下一次API调用>再花费6秒钟>等待另外3秒钟,以此类推。这意味着每次轮询之间的间隔为9秒。我理解得对吗? - 7ball
@7ball,是的,没错。如果你有一个非常长时间运行的API,无论需要多长时间,都将包含在每个循环中。你可以使用间隔每隔n秒触发一次操作,但最好避免重叠的相同调用。如果你的API调用需要6秒钟,而你不想等待其他3秒钟,你可以减少额外的等待时间。 - KyleMit
1
不,这实际上正是我所需要的,非常感谢!针对您的观点,如果上一个轮询仍在进行中,我不希望下一个轮询发生。我只想确保最少每3秒进行一次API调用。 - 7ball
1
凌晨两点,我终于可以去睡觉了。非常感谢! - nguaman

5
以下是一个使用Promise和轮询直到获取所需结果的函数示例,还可以将轮询间隔和超时值作为参数传入:
```javascript 这里是一段使用 Promise 并且通过轮询来获取所需结果的函数示例。同时,我也将其参数化,以便您可以传入轮询间隔和超时值:
```
// create a promise that resolves after a short delay
function delay(t) {
    return new Promise(function(resolve) {
        setTimeout(resolve, t);
    });
}

// interval is how often to poll
// timeout is how long to poll waiting for a result (0 means try forever)
// url is the URL to request
function pollUntilDone(url, interval, timeout) {
    let start = Date.now();
    function run() {
        return client.get(url).then(function(dataResult) {
            if (dataResult.status === "DONE") {
                // we know we're done here, return from here whatever you 
                // want the final resolved value of the promise to be
                return dataResult;
            } else {
                if (timeout !== 0 && Date.now() - start > timeout) {
                    throw new Error("timeout error on pollUntilDone");
                } else {
                    // run again with a short delay
                    return delay(interval).then(run);
                }
            }
        });
    }
    return run();
}

// sample usage
// polls every 500ms for up to 30 seconds
pollUntilDone(someUrl, 500, 30 * 1000).then(function(result) {
   // have final result here 
}).catch(function(err) {
    // handle error here
});

关键在于链接你的承诺,这样每次再次调用run()时,你会返回它的值,以便它链接到前一个promise。然后,无论何时你最终返回一个值或抛出异常,原始promise都将得到该值或错误作为解析值或拒绝原因。
请注意,我添加了超时,因为你真的不想永远轮询,特别是在某些意外和反复出现的错误可能不会拒绝承诺的情况下,但也无法获得所需的结果。

如何重构以避免嵌套承诺? - JSDBroughton
@Jonathon - 你在说什么嵌套?这是伪递归,而不是嵌套。如果你要在.then()内部进行条件分支,你基本上必须按照这里的方式来做。我不明白你想要删除哪个嵌套? - jfriend00
非常抱歉 - 发错了页面 - 一年前的帖子回复速度惊人 ;) - JSDBroughton

5
最近版本的Node.js已经支持async/await。
下面是一个使用它的例子。
async/await的一个主要优势是,它非常容易跟踪代码并理解其逻辑。例如,如果您想将其扩展为具有最大尝试功能,则很容易实现(提示:只需要一个for循环)。

let count = 0;

var client = {
  get: function () {
    return new Promise(function (resolve, reject) {
      count ++;
      setTimeout(function () {
        if (count > 4) resolve({status:'DONE',otherStuff:'Other Stuff'});
        else resolve({status: `count: ${count}`});
      }, 1000);
    });
  }
}


async function someFunction() {
  while (true) {
    let dataResult = await client.get('/status');
    console.log(dataResult.status);
    if (dataResult.status == "DONE") {
      return dataResult;
    }
  }
}

(async () => { let r = await someFunction(); console.log(r); })();


3

我之前遇到了类似的问题。 下面是我的解决方案概述:

// api call with interval until receiving a specific data.

const callApi = () => {
  return new Promise((resolve, reject) => {
    console.log('calledAPI!');
    setTimeout(()=>{
      var myNumber = parseInt(Math.random()*10, 10);
      resolve(myNumber);
    }, 1000);
  });
}

const callIntervalFunc = () => { // need to wrap it with a function because setInterval is not a function and cannot call it from outside otherwise.
  const callInverval = setInterval(async()=>{
    console.log('checking the api...');

    var responseNumber = await callApi();
    console.log('Got response! ',responseNumber);
    if (responseNumber === 5) {
      clearInterval(callInverval);
      console.log('responseNumber is 5!! ends.');
    }
  }, 2000);
}

callIntervalFunc();


1
谢谢!这个解决方案对我有用。对其他人来说可能很明显,但如果你在callApi的内部等待API响应,你需要在传递给new Promise的函数前添加async - fastredshoes

1
这里有一个简单的替代方案。
(function poll(){

    //do the check - ajax, etc.

    if (condition_that_means_were_done) {
        return
    }   
        
    setTimeout(poll,timeout_ms)
})();

1

下面的continuousPromise方法可以完成这个任务,它需要两个参数:

  1. Promise responsible for fetching data

  2. delay (in ms) in subsequent calls

    let exit = false;
    const continuousPromise = (promise, interval)  => {
        const execute = () => promise().finally(waitAndExecute);
        const waitAndExecute = () => {
            if (exit) {
                return;
            }
            setTimeout(execute, interval)
        };
        execute();
    }
    
如何使用
continuousPromise(() => {
    return axios.get("something.json")
        .then((res) => {
            if (res && res.status !== 'PENDING') { // Check here for expected result
                exit = true;
            }
        })
        .catch((error) => {
            exit = true;
            console.log(error);
        })
}, 1000);

0
someFunction() 中,它返回一个 new Promise,该 Promise 发出一个 resolve() 并将处理后的结果作为参数传递。在 getpolledresult() 中,捕获处理后的结果以确定是否进行轮询。

function getSearchResults(term) {
  return new Promise(resolve => {
    let timeout = 100 + Math.floor(Math.random() * 1900);
    console.log("is number < 500?", timeout);
    let result = {
      status: "",
      term_lower: term.toLowerCase(),
      term_upper: term.toUpperCase()
    };
    if (timeout < 500) {
      result.status = "DONE"
    }
    setTimeout(() => resolve(result), timeout);
  });
}

let cancelCallback = () => {};
let cancelflag = 0;

var sleep = (period) => {
  return new Promise((resolve) => {
    cancelCallback = () => {
      console.log("timeout...");
      // send cancel message...
      cancelflag = 1;
      return resolve('Canceled');
    }
    setTimeout(() => {
      resolve("tick");
    }, period)
  })
}

let asleep = async (period) => {
  let respond = await sleep(period);
  // if you need to do something as soon as sleep finished
  /* console.log("sleep just finished, do something...") */
  return respond;
}


function someFunction() {
  return new Promise((resolve) => {
    console.log("polling...");
    /* let a = client.get('/status'); */
    let a = getSearchResults('a');
    let processedResult = {
      status: ""
    };
    a.then(dataResult => {
      if (dataResult.status == "DONE") {
        //** want to get other values in dataResult here
        // and store it somewhere else for use later
        processedResult.status = "OK";
      };
      resolve(processedResult);
    });
  });
}


var getpolledresult = (promiseFn, period, timeout) => promiseFn().then((result) => {
  // ***want to work with results here.
  // need some way to get the status of what happened inside the .then(..) part
  //  eventually have to return success or failure and results to the frontend
  // (this part is already done)
  console.log(result);

  if (result.status !== "OK") {
    asleep(period).then((respond) => {
      // check if sleep canceled, if not, continue to poll
      if (cancelflag !== 1) {
        poll(promiseFn, period, timeout);
      }
    })
  }

});


var poll = (promiseFn, period, timeout) => {
  // just check if cancelCallback is empty function, 
  // if yes, set a time out to run cancelCallback()
  if (cancelCallback.toString() === "() => {}") {
    console.log("set timout...")
    setTimeout(() => {
      cancelCallback()
    }, timeout);
  }

  getpolledresult(promiseFn, period, timeout);
}


poll(someFunction, 1000, 10000);


0

一种选项是修改poll函数,只有在满足您要求的条件时才会解决:

function poll(pollFn, interval = 100) {
    var intervalHandle = null

    return {
        until(conditionFn) {
            return new Promise((resolve, reject) => {
                intervalHandle = setInterval(() => {
                    pollFn().then((data) => {
                        let passesCondition = false;
                        try {
                            passesCondition = conditionFn(data);
                        } catch(e) {
                            reject(e);
                        }
                        if (passesCondition) {
                            resolve(data);
                            clearInterval(intervalHandle);
                        }
                    }).catch(reject)
                }, interval)
            })
        }
    }
}

var counter = 0;

function getStatus() {
    if (counter++ === 5) {
       return Promise.resolve({ status: 'DONE', otherStuff: 'hi' });
    }
    console.log('not DONE, keep going')
    return Promise.resolve({ status: 'WORKING' });
}

poll(getStatus, 500)
  .until(data => data.status === 'DONE')
  .then((data) => {
    // do something with the data
    console.log('status is DONE', data)
  })


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接