ウェブサイト検索

javascript - 非同期関数 + await + setTimeout の組み合わせ


私は新しい非同期機能を使おうとしており、問題を解決することが将来他の人々の助けになることを望んでいます。これは動作している私のコードです:

  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 に秒間であまりにも多くのリクエストを送ってしまうことです。したがって、リクエストを遅延させるスリープ関数を作りたいと考えています。これにより、他のリクエストを遅延させるためにもこの関数を使用できます。もしリクエストを遅延させる別の方法があれば、教えてください。

とにかく、これは私の新しいコードですが、動作しません。リクエストのレスポンスは 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 関数から返す、匿名関数内でのコールバックなどです。

回答 #1

あなたの sleep 関数は動作しません。なぜなら setTimeout はまだ(あるいは現時点では)await 可能な Promise を返さないからです。手動で Promise 化する必要があります:

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秒かかるようになります。

回答 #2

クイックな一行、インライン方式

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

回答 #3

Node 7.6以降、ユーティリティモジュールのpromisify関数をsetTimeout()と組み合わせて使用することができます。

Node.js

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

ジャバスクリプト

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

使用法

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

回答 #4

タイマーPromises API

Node.js 16でついにawait setTimeoutが登場し、util.promisify()を使う必要がなくなりました:

import { setTimeout } from 'timers/promises';

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

公式Node.jsドキュメント:タイマー プロミス API(Nodeにすでに組み込まれているライブラリ)

回答 #5

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 と組み合わせることについてだけ質問に答えていますが、1 秒あたりのリクエストが多すぎるという問題の解決には役立たないかもしれません。

更新: 最新の node.js バージョンには、util.promisify ヘルパーを通じてアクセスできる組み込みの非同期タイムアウト実装があります:

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

回答 #6

もし 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

回答 #7

API 呼び出し(例:クライアントを取得)を setTimeout で行いたい人のために、このコードスニペットをここに置いておきます:

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

回答 #8

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

関連記事