使用setInterval实现异步等待

58
function first(){
  console.log('first')
}
function second(){
  console.log('second')
}
let interval = async ()=>{
  await setInterval(first,2000)
  await setInterval(second,2000)
}
interval();

假设我有上面这段代码。

当我运行它时,first()second()将同时被调用;如何在first()返回一些数据后调用second(),例如,如果first()完成了,只有在该函数完成之后才调用second()?

因为我的代码中的first()将使用大量的数据,如果这两个函数同时调用,服务器将很难承受。

first()返回一些数据时,如何每次调用second()


2
setInterval永远不会解决问题,因为它是一个持续重复器。我不确定您是否经过深思熟虑。您究竟想要实现什么?如果您想每2秒钟运行调用并对其做出反应,那么最好编写一个使用RxJs和可观察对象的解决方案。 - Lars Holdaas
你能解释一下你的使用情况吗? - k0pernikus
当服务器启动时,我想以一定的时间间隔调用一些函数,例如每15分钟调用一次。但是这些函数处理大量数据,我可以同时运行2个函数,但我认为这对服务器来说很困难。我希望以相同的时间间隔运行函数,但在第一个函数完成后等待...例如,我有第一个函数,在15分钟后执行此函数,它被执行,第二个函数等待,只有在第一个函数完成后,第二个函数才开始,以此类推。希望清楚明白。 - Andrey Radkevich
10个回答

69

如上所述,setInterval如果不停止它,则与promise不兼容。 如果清除间隔,则可按如下方式使用:

async function waitUntil(condition) {
  return await new Promise(resolve => {
    const interval = setInterval(() => {
      if (condition) {
        resolve('foo');
        clearInterval(interval);
      };
    }, 1000);
  });
}

稍后您可以像这样使用它

const bar = waitUntil(someConditionHere)

4
虽然我认为所提出的问题并不清楚,但是这个答案指出了几个人所说的“setInterval”与承诺不兼容的谬论;如果提供了正确的逻辑,它可以很好地运行(就像任何代码都有其自己的要求以正确运行一样)。我修复了一些语法错误,但我认为这个答案的要点提供了比其他答案更好的信息。(我不认为它真正回答了原始问题,但我不确定我自己确切知道那个问题在问什么。) - Zhora
1
条件应该是一个回调函数。 - Long Nguyen
非常感谢这个!谢谢。 - Carlo Nyte
3
这里,bar将立即被分配一个挂起的Promise,并且不会等待它解决。 - Kameneth
@Kameneth 你的意思是最后一行应该是:const bar = async waitUntil(someConditionHere) 吗? - icc97
显示剩余2条评论

44

你有几个问题:

  1. Promise 只能够被解决(resolve)一次,`setInterval()` 用于多次调用回调函数,Promise 并不支持这种情况。
  2. `setInterval()` 和更适合的 `setTimeout()` 都不会返回 Promise,因此,在这种情况下等待它们是没有意义的。

你需要寻找一个使用 `setTimeout()` (而不是 `setInterval()`)在一段时间后返回 Promise 的函数。

幸运的是,创建这样一个函数相当简单:

async function delay(ms) {
  // return await for better async stack trace support in case of errors.
  return await new Promise(resolve => setTimeout(resolve, ms));
}

有了这个新的delay函数,您可以实现所需的流程:

function first(){
  console.log('first')
}
function second(){
  console.log('second')
}
let run = async ()=>{
  await delay(2000);
  first();
  await delay(2000)
  second();
}
run();

4
“delay” 应该只是 util.promisify(setTimeout) - Benjamin Gruenbaum
1
这在技术上并不相同,因为运行时间可能会有所不同,导致间隔不一致。 - Sander van den Akker

8

setInterval和promise不兼容,因为它会多次触发回调函数,而promise只解决一次。

看起来应该使用setTimeout。它应该被promisified以便与async..await一起使用:

async () => {
  await new Promise(resolve => setTimeout(() => resolve(first()), 2000));
  await new Promise(resolve => setTimeout(() => resolve(second()), 2000));
}

4

您可以使用IIFE。这样,您就可以避免myInterval不接受Promise作为返回类型的问题。

有些情况下,您需要使用setInterval,因为您想以一定间隔调用某些函数未知次数。 当我面临这个问题时,这是对我来说最直接的解决方案。希望它能帮到别人:)

对于我来说,用例是我想将日志发送到CloudWatch,但尝试避免每秒发送5条以上日志的限制异常。所以我需要保留我的日志,并在1秒间隔内作为批处理发送它们。我在此发布的解决方案即是我最终使用的方案。

  async function myAsyncFunc(): Promise<string> {
    return new Promise<string>((resolve) => {
      resolve("hello world");
    });
  }

  function myInterval(): void {
    setInterval(() => {
      void (async () => {
        await myAsyncFunc();
      })();
    }, 5_000);
  }

  // then call like so
  myInterval();

4

await表达式会导致异步操作暂停,直到一个Promise对象被解析

因此你可以直接获取Promise的结果而不必使用await

对于我来说,我想每1秒发起一次Http请求

let intervalid 
async function testFunction() {
    intervalid = setInterval(() => {
        // I use axios like: axios.get('/user?ID=12345').then
        new Promise(function(resolve, reject){
            resolve('something')
        }).then(res => {
            if (condition) {
               // do something 
            } else {
               clearInterval(intervalid)
            }    
        })  
    }, 1000)  
}
// you can use this function like
testFunction()
// or stop the setInterval in any place by 
clearInterval(intervalid)


1
我不明白为什么这里要用 async 关键字。另外,如果你的 Promise 花费超过一秒钟来解决,一个新的 Promise 将在清除间隔之前被创建(如果需要的话)。 - Kameneth

3

浏览了所有答案,但仍未找到与 OP 所要求的完全相符的正确答案。这是我用于相同目的的内容:

async function waitInterval(callback, ms) {
    return new Promise(resolve => {
        let iteration = 0;
        const interval = setInterval(async () => {
            if (await callback(iteration, interval)) {
                resolve();
                clearInterval(interval);
            }
            iteration++;
        }, ms);
    });
}

function first(i) {
    console.log(`first: ${i}`);
    // If the condition below is true the timer finishes
    return i === 5;
}

function second(i) {
    console.log(`second: ${i}`);
    // If the condition below is true the timer finishes
    return i === 5;
}

(async () => {
    console.log('start');
    await waitInterval(first, 1000);
    await waitInterval(second, 1000);
    console.log('finish');
})()

在我的例子中,我还附加了间隔迭代计数和定时器本身,以防调用者需要对其进行操作。但是,这并不是必要的。


0
在我的情况下,我需要迭代通过一系列图片,在每个之间暂停一下,最后在重新循环前长时间暂停。 我通过结合以上的几种技术,递归调用我的函数并等待超时来实现这一点。 如果在任何时候另一个触发器更改了我的animationPaused:boolean,我的递归函数将退出。
    const loopThroughImages = async() => {
      for (let i=0; i<numberOfImages; i++){
        if (animationPaused) {
          return;
        }
        this.updateImage(i);
        await timeout(700);
      }
      await timeout(1000);
      loopThroughImages();
    }

    loopThroughImages();

将递归与循环结合起来使用虽然可以实现,但感觉很奇怪。为什么不直接嵌套两个循环呢? - Bergi
这并没有回答问题。一旦您拥有足够的声望,您将能够评论任何帖子;相反,提供不需要询问者澄清的答案。- 来自审核 - 8ctopus

0

0

Async/await并不会使promise同步化。 据我所知,它只是return Promise.then()的不同语法。 在这里,我重写了async函数并留下了两个版本,这样你就可以看到它真正做了什么并进行比较。 实际上,它是一系列的Promise级联。

// by the way no need for async there. the callback does not return a promise, so no need for await.
function waitInterval(callback, ms) {
    return new Promise(resolve => {
        let iteration = 0;
        const interval = setInterval(async () => {
            if (callback(iteration, interval)) {
                resolve();
                clearInterval(interval);
            }
            iteration++;
        }, ms);
    });
}

function first(i) {
    console.log(`first: ${i}`);
    // If the condition below is true the timer finishes
    return i === 5;
}

function second(i) {
    console.log(`second: ${i}`);
    // If the condition below is true the timer finishes
    return i === 5;
}

// async function with async/await, this code ...
(async () => {
    console.log('start');
    await waitInterval(first, 1000);
    await waitInterval(second, 1000);
    console.log('finish');
})() //... returns a pending Promise and ...
console.log('i do not wait');

// ... is kinda identical to this code.
// still asynchronous but return Promise statements with then cascade.
(() => {
    console.log('start again');
    return waitInterval(first, 1000).then(() => {
        return waitInterval(second, 1000).then(() => {
                console.log('finish again');
        });
    });
})(); // returns a pending Promise...
console.log('i do not wait either');

您可以看到两个异步函数同时执行。 因此,在这里使用间隔周围的承诺并不是非常有用,因为它仍然只是间隔,并且承诺并没有改变任何东西,反而让事情变得混乱...
由于代码重复调用回调到一个间隔中,我认为这是一个更清晰的方法:

function first(i) {
    console.log(`first: ${i}`);
    // If the condition below is true the timer finishes
    return i === 5;
}

function second(i) {
    console.log(`second: ${i}`);
    // If the condition below is true the timer finishes
    return i === 5;
}

function executeThroughTime(...callbacks){
    console.log('start');
    let callbackIndex = 0; // to track current callback.
    let timerIndex = 0; // index given to callbacks
    let interval = setInterval(() =>{
        if (callbacks[callbackIndex](timerIndex++)){ // callback return true when it finishes.
            timerIndex = 0; // resets for next callback
            if (++callbackIndex>=callbacks.length){ // if no next callback finish.
                clearInterval(interval);
                console.log('finish');
            }
        }
    },1000)
}

executeThroughTime(first,second);
console.log('and i still do not wait ;)');

此解决方案每秒执行一次回调。 如果回调是需要超过一秒才能解决的异步请求,并且我不能承受它们重叠,那么,我将获取请求分辨率以调用下一个请求(通过计时器,如果我不想骚扰服务器)。
这里“递归”任务称为lTask,与以前几乎相同,只是因为我不再有间隔,所以每次迭代都需要一个新的计时器。

// slow internet request simulation. with a Promise, could be a callback.
function simulateAsync1(i) {
    console.log(`first pending: ${i}`);
    return new Promise((resolve) =>{
        setTimeout(() => resolve('got that first big data'), Math.floor(Math.random()*1000)+ 1000);//simulate request that last between 1 and 2 sec.
    }).then((result) =>{
        console.log(`first solved: ${i} ->`, result);
        return i==2;
    });
}
// slow internet request simulation. with a Promise, could be a callback.
function simulateAsync2(i) {
    console.log(`second pending: ${i}`);
    return new Promise((resolve) =>{
        setTimeout(() => resolve('got that second big data'), Math.floor(Math.random()*1000) + 1000);//simulate request that last between 1 and 2 sec.
    }).then((result) =>{ // promise is resolved
        console.log(`second solved: ${i} ->`,result);
        return i==4; // return a promise
    });
}

function executeThroughTime(...asyncCallbacks){
    console.log('start');
    let callbackIndex = 0;
    let timerIndex = 0;
    let lPreviousTime = Date.now();
    let lTask = () => { // timeout callback.
        asyncCallbacks[callbackIndex](timerIndex++).then((result) => { // the setTimeout for the next task is set when the promise is solved.
            console.log('result',result)
            if (result) { // current callback is done.
                timerIndex = 0;
                if (++callbackIndex>=asyncCallbacks.length){//are all callbacks done ?
                    console.log('finish');
                    return;// its over
                }
            }
            console.log('time elapsed since previous call',Date.now() - lPreviousTime);
            lPreviousTime = Date.now();
            //console.log('"wait" 1 sec (but not realy)');
            setTimeout(lTask,1000);//redo task after 1 sec.
            //console.log('i do not wait');
        });
    }
    lTask();// no need to set a timer for first call.
}

executeThroughTime(simulateAsync1,simulateAsync2);
console.log('i do not wait');

下一步是按照时间间隔清空一个FIFO,并用Web请求Promise填充它...

0
setInterval安排了间隔时间,但是异步调用可能需要更长时间,或者最终与所需的间隔时间不同步。
最简单的解决方案是在await完成后调用setTimeout。

const first = async () => console.log('first');
const second  = async () => console.log('second');

const interval = async () => {
  await first();
  await second();
  // All done, schedule a new tick
  setTimeout(interval, 2000);
}

interval();

PS:
根据上述情况,等待时间可能超过2000毫秒。
...? ms time to complete fn1
...? ms time to complete fn2
...2000 ms timeout

为了在每2秒钟内及时调用您的函数,您需要从2000毫秒中减去完成异步函数所花费的时间。

const first = async() => console.log('first');
const second = async() => console.log('second');

const interval = async() => {
  const timeStart = Date.now();

  await first();
  await second();

  // All done, schedule a new one
  const timeDiff = Date.now() - timeStart;
  const delay = Math.max(0, 2000 - timeDiff);
  setTimeout(interval, delay);
}

interval();


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