【问题标题】:Polling until getting specific result?轮询直到得到具体结果?
【发布时间】:2017-09-13 22:56:41
【问题描述】:

我目前正在尝试使用此链接 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。

【问题讨论】:

    标签: javascript node.js asynchronous


    【解决方案1】:

    这是一个基于polling with 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);
    

    【讨论】:

    • 最干净的解决方案!谢谢
    • 那么这是否意味着如果fn 需要很长时间才能完成,那么在fn 返回之前不会进行下一次投票,对吗?例如,假设fetchReport 需要 6 秒才能完成,ms 是 3000(或 3 秒)。这意味着 API 调用需要 6 秒 > 再等待 3 秒以进行下一次 API 调用 > 再需要 6 秒 > 再等待 3 秒,等等。所以这意味着每次轮询之间的间隔是 9 秒。我理解对了吗?
    • @7ball,是的,没错。如果您有一个运行时间很长的 API,那么无论它花费的时间都将包含在每个循环中。您可以每n 秒以 interval 触发一些东西,但最好避免重叠相同的调用。如果您的 API 调用需要 6 秒,并且您不想等待其他 3 秒,则可以减少额外的等待时间
    • 不,这实际上正是我所需要的,非常感谢!就您的观点而言,如果前一个民意调查仍在进行,我不希望下一次民意调查发生。我只想确保至少每 3 秒调用一次 API。
    【解决方案2】:

    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】:

      这是一个函数示例,该函数使用 promises 和 polls 直到你得到想要的结果。我还对其进行了参数化,以便您可以传入轮询间隔和超时值:

      // 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
      });
      

      这里的关键是链接你的 Promise,这样每次你再次调用 run() 时,你都会返回它的值,这样它就会被链接到之前的 Promise 上。然后,每当你最终返回一个值或抛出一个异常时,原始的 Promise 都会将该值或错误作为已解决的值或被拒绝的原因。

      请注意,我添加了一个超时,因为你真的不想永远轮询,特别是在某些不可预见和重复出现的错误可能不会拒绝承诺,但不会得到你想要的结果的情况下。

      【讨论】:

      • 如何重构不嵌套承诺?
      • @Jonathon - 你在说什么嵌套?这是伪递归的,不是嵌套的。如果您要在.then() 内进行条件分支,您几乎必须按照这里的方式进行。我不明白您要删除什么嵌套?
      • 很抱歉 - 发布到错误的页面 - 一年前的帖子令人印象深刻的响应时间;)
      【解决方案4】:

      我只需要解决一个类似的问题。 以下是我的解决方案的要点:

      // 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();

      【讨论】:

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

      以下continuousPromise 方法将完成这项工作,它需要2个参数:

      1. Promise 负责获取数据

      2. 后续调用的延迟(以毫秒为单位)

        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);
      

      【讨论】:

        【解决方案6】:

        一种选择是将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)
          })

        【讨论】:

          【解决方案7】:

          在 someFunction() 中,它返回一个新的 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);

          【讨论】:

            【解决方案8】:

            这里有一个简单的替代方法

            (function poll(){
            
                //do the check - ajax, etc.
            
                if (condition_that_means_were_done) {
                    return
                }   
                    
                setTimeout(poll,timeout_ms)
            })();
            

            【讨论】:

              猜你喜欢
              • 2017-03-08
              • 2021-09-25
              • 1970-01-01
              • 2021-07-24
              • 2020-01-09
              • 2019-05-31
              • 1970-01-01
              • 2020-02-12
              • 2013-05-30
              相关资源
              最近更新 更多