轮询直到得到具体结果?

Posted

技术标签:

【中文标题】轮询直到得到具体结果?【英文标题】: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。

【问题讨论】:

【参考方案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个参数:

    Promise 负责获取数据

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

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

【讨论】:

以上是关于轮询直到得到具体结果?的主要内容,如果未能解决你的问题,请参考以下文章

预定执行器:以固定速率轮询结果,如果超时或结果有效则退出

以异步方式实现长轮询

ajax轮询

长轮询如何工作 javascript?

Java 和 Spring - 轮询 http 端点,直到服务器完成处理

jQuery AJAX 轮询 JSON 响应,基于 AJAX 结果或 JSON 内容进行处理