使用异步函数+await+setTimeout的组合

693

我正在尝试使用新的异步特性,并希望解决我的问题能帮助未来的其他人。这是我目前可用的代码:

  async function asyncGenerator() {
    // other code
    while (goOn) {
      // other code
      var fileList = await listFiles(nextPageToken);
      var parents = await requestParents(fileList);
      // other code
    }
    // other code
  }

  function listFiles(token) {
    return gapi.client.drive.files.list({
      'maxResults': sizeResults,
      'pageToken': token,
      'q': query
    });
  }
问题在于我的 while 循环运行得太快,脚本向 Google API 发送了过多的请求。因此,我希望构建一个延迟请求的 sleep 函数。这样,我也可以使用此函数来延迟其他请求。如果有另一种延迟请求的方法,请告诉我。
无论如何,这是我的新代码,但它不起作用。请求的响应返回给 setTimeout 中的匿名异步函数,但我不知道如何将响应返回到 sleep 函数或初始的 asyncGenerator 函数。
  async function asyncGenerator() {
    // other code
    while (goOn) {
      // other code
      var fileList = await sleep(listFiles, nextPageToken);
      var parents = await requestParents(fileList);
      // other code
    }
    // other code
  }

  function listFiles(token) {
    return gapi.client.drive.files.list({
      'maxResults': sizeResults,
      'pageToken': token,
      'q': query
    });
  }

  async function sleep(fn, par) {
    return await setTimeout(async function() {
      await fn(par);
    }, 3000, fn, par);
  }

我已经尝试了一些选项:将响应存储在全局变量中,并从sleep函数返回它,匿名函数内的回调等。

19个回答

1165

你的 sleep 函数无法工作,因为 setTimeout 目前还没有返回可以被 await 的 promise。你需要手动将其 promisify:

function timeout(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}
async function sleep(fn, ...args) {
    await timeout(3000);
    return fn(...args);
}

顺便提一下,如果你想要减慢循环速度,你可能不希望使用像这样需要回调和延迟的sleep函数。我建议:

while (goOn) {
  // other code
  var [parents] = await Promise.all([
      listFiles(nextPageToken).then(requestParents),
      timeout(5000)
  ]);
  // other code
}

这使得计算parents至少需要5秒钟。


37
喜欢Promise.all的方法。如此简单而优雅! - Anshuul Kai
6
var [parents] 这个符号表示什么?我之前从未见过,而且很难在谷歌上搜索到相关信息。 - natedog
10
@NateUsher 这是数组解构 - Bergi
7
如果需要等待,"@tinkerr timeout needs to be declared async if it needs to be awaited"是错误的。一个函数只需要返回一个可等待的承诺(或者实际上,一个 thenable 就足够了)。如何实现这一点取决于函数的具体实现,它不需要是一个 "async function"。 - Bergi
3
不,async/await基于Promise。它所替代的仅仅是then调用。 - Bergi
显示剩余12条评论

576

一种快速的单行内联方式

 await new Promise(resolve => setTimeout(resolve, 1000));

13
当你们在同一行中两次使用“resolve”时,它是什么意思?例如:await new Promise(resolve => setTimeout(resolve, 1000)); 它是指它本身还是其他什么?我会改成这样:function myFunc(){}; await new Promise(resolve => setTimeout(myFunc, 1000));意思是在Promise函数的执行过程中,第一个"resolve"是创建一个回调函数,并将其作为参数传递给Promise构造函数。当定时器完成后,该回调函数将被调用以解决(即完成)Promise。第二个"resolve"是指回调函数的参数,它是Promise的解决函数,用于将Promise从未解决状态转换为已解决状态。因此,这种用法是正确的,并且不能被替换为函数名,因为回调函数必须接受解决函数作为参数。 - PabloDK
13
@PabloDK 你可以将上面的一行简洁代码扩展为这个(更冗长)版本,希望这样可以很明显地看出为什么 resolve 出现了两次。如果还是有疑惑,可以查看 Promise 的 MDN 文档 - mrienstra
7
“@PabloDK,这段内容也可以这样表示:await new Promise((resolve, reject) => setTimeout(resolve, 1000));。因此,当你创建一个Promise时,resolvereject是暴露的回调函数。你只是在告诉setTimeout执行resolve()。” - crimson_king
2
你应该获得诺贝尔奖 - jimmystackoverflowjiim
1
所以一开始我并不太明白如何使用这个单行代码,但如果我没弄错的话,它基本上允许我在一个for循环之前的单行代码中添加超时,因此我不需要包装任何东西?它似乎是这样工作的,我只是想确保它以后不会失败。我基本上需要在for循环开始之前延迟10秒钟,所以我将其放在我的for循环上方,并将1000更改为10000,它似乎完美地工作了。我有什么遗漏的吗? - garek007
显示剩余3条评论

255

自Node 7.6以来,您可以将utils模块中的promisify函数与setTimeout()结合使用。

Node.js


Node.js

const sleep = require('util').promisify(setTimeout)

Javascript

const sleep = m => new Promise(r => setTimeout(r, m))

用法

(async () => {
    console.time("Slept for")
    await sleep(3000)
    console.timeEnd("Slept for")
})()

2
在NodeJS中,即使不使用require,也可以通过await setTimeout[Object.getOwnPropertySymbols(setTimeout)[0]](3000)实现类似于await require('util').promisify(setTimeout)(3000)的效果。 - Shl
12
有趣,@Shl。我认为它比我的解决方案不够易读。如果有人不同意,我可以将其添加到解决方案中吗? - Harry
2
所需版本明显比getOwnPropertySymbols版本好得多...如果它没有出问题...! - Matt Fletcher
2
@FélixGagnon-Grenier 为什么要回滚,让他保留他的抄袭。好的想法应该被复制。 - gregn3
5
我已经删除了一行代码,因为答案就在下面,但是我看到很多受欢迎的答案会更新他们的答案以包括其他新的答案,因为大多数读者不会去看超过前几个回复。 - Harry
显示剩余7条评论

61

计时器承诺API

await setTimeout终于在Node.js 16中到来,不再需要使用util.promisify()

import { setTimeout } from 'timers/promises';

(async () => {
  const result = await setTimeout(2000, 'resolved')
  // Executed after 2 seconds
  console.log(result); // "resolved"
})()

官方的Node.js文档:定时器 Promises API(已经内置在Node中的库)


57

setTimeout不是async函数,因此您不能与ES7的async-await一起使用它。但是,您可以使用ES6的Promise实现自己的sleep函数:

function sleep (fn, par) {
  return new Promise((resolve) => {
    // wait 3s before calling fn(par)
    setTimeout(() => resolve(fn(par)), 3000)
  })
}

那么您就可以使用这个新的 sleep 函数和 ES7 async-await:

var fileList = await sleep(listFiles, nextPageToken)

请注意:我只回答你有关结合ES7 async/await和setTimeout的问题,尽管这可能无法解决您每秒发送太多请求的问题。


更新:现代node.js版本具有内置的异步超时实现,可通过util.promisify助手访问:

const {promisify} = require('util');
const setTimeoutAsync = promisify(setTimeout);

2
fn 抛出错误时,不应该这样做,因为它不会被捕获。 - Bergi
@Bergi 我认为它会冒泡到new Promise,在那里你可以使用sleep.catch捕获它。 - Florian Wendelborn
4
不,它在一个异步的setTimeout回调函数中,并且new Promise的回调函数已经完成了。 它将冒泡到全局上下文并作为未处理的异常抛出。 - Bergi
1
发送过多请求的问题。您可能想使用“防抖”来防止诸如 UI 触发过多请求等情况。 - FlavorScape
settimeout是异步的,点击这里阅读更多信息:https://developer.mozilla.org/zh-CN/docs/Web/API/setTimeout - OriEng

17

如果您想使用与setTimeout相同的语法,您可以编写一个类似这样的帮助函数:

如果你想要使用和setTimeout一样的语法,你可以写一个像这样的辅助函数:

const setAsyncTimeout = (cb, timeout = 0) => new Promise(resolve => {
    setTimeout(() => {
        cb();
        resolve();
    }, timeout);
});
您可以这样调用它:
const doStuffAsync = async () => {
    await setAsyncTimeout(() => {
        // Do stuff
    }, 1000);

    await setAsyncTimeout(() => {
        // Do more stuff
    }, 500);

    await setAsyncTimeout(() => {
        // Do even more stuff
    }, 2000);
};

doStuffAsync();

我做了一个要点总结:https://gist.github.com/DaveBitter/f44889a2a52ad16b6a5129c39444bb57


1
一个像 delayRun 这样的函数名在这里更有意义,因为它将延迟回调函数 X 秒后再运行。在我看来,这不是一个非常 await-ey 的例子。 - mix3d

16

我在这里留下这段代码片段,供那些想要使用 setTimeout 获取 API 调用(例如获取客户端)的人使用:

const { data } = await new Promise(resolve => setTimeout(resolve, 250)).then(() => getClientsService())
setName(data.name || '')
setEmail(data.email || '')

2
这应该是被接受的答案。 - Ardee Aram

10
await new Promise(resolve => setTimeout(() => { resolve({ data: 'your return data'}) }, 1000))

8
var testAwait = function () {
    var promise = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('Inside test await');
        }, 1000);
    });
    return promise;
}

var asyncFunction = async function() {
    await testAwait().then((data) => {
        console.log(data);
    })
    return 'hello asyncFunction';
}

asyncFunction().then((data) => {
    console.log(data);
});

//Inside test await
//hello asyncFunction

5

这是我在2020年使用Node.js版本的AWS Lambda。

const sleep = require('util').promisify(setTimeout)

async function f1 (some){
...
}

async function f2 (thing){
...
}

module.exports.someFunction = async event => {
    ...
    await f1(some)
    await sleep(5000)
    await f2(thing)
    ...
}

promisify对你的自定义sleep函数中的setTimeout做了什么,使其不再需要一个函数作为第一个参数?例如,如果你运行setTimeout(5000);(没有一个函数作为第一个参数),你会得到Uncaught TypeError [ERR_INVALID_CALLBACK]: Callback must be a function. Received 5000 - Lonnie Best

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