Сочетание асинхронной функции + ожидание + setTimeout

307

Я пытаюсь использовать новые асинхронные функции и надеюсь, что решение моей проблемы поможет другим в будущем. Это мой код, который работает:

  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, но я просто не знаю, как я могу вернуть ответ на функцию сна, соответственно. к начальной функции 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);
  }

Я уже попробовал некоторые варианты: сохранение ответа в глобальной переменной и возврат его из функции сна, обратный вызов в анонимной функции и т. Д.

JShinigami
источник

Ответы:

615

Ваша sleepфункция не работает, потому setTimeoutчто (еще?) Не возвращает обещание, которое можно awaitредактировать. Вам нужно будет обещать это вручную:

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 секунд.

Берги
источник
11
Люблю Promise.allподход. Так просто и элегантно!
Аншул Кока
4
что обозначение var [parents]представляет? Я не видел этого раньше, и это трудно для Google
Natedog
6
@NateUsher Это разрушение массива
Берги
1
@tinkerr « потребность тайма - аут , чтобы быть объявлена асинхронной , если он должен быть ожидаемым » - Нео. Функция должна только возвращать обещание, которое можно ожидать (или на самом деле, этого достаточно). Как это достигается, что зависит от реализации функции, он не должен быть async function.
Берги
2
@naisanza Нет, async/ awaitэто основано на обещаниях. Единственное, что он заменяет, это thenзвонки.
Берги
152

Начиная с узла 7.6 , вы можете комбинировать функцию promisifyфункций из модуля utils с setTimeout().

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")
})()
Гарри
источник
1
В nodeJS await require('util').promisify(setTimeout)(3000)также можно достичь без необходимости:await setTimeout[Object.getOwnPropertySymbols(setTimeout)[0]](3000)
ЗЫ
5
Интересно @Shl. Я думаю, что это менее читабельно, чем мое решение, хотя. Если люди не согласны, я могу добавить это к решению?
Гарри
2
Требуемая версия явно намного лучше, чем getOwnPropertySymbolsверсия ... если она не сломана ...!
Мэтт Флетчер
2
Привет, @Гарри. Похоже, вы включили один вкладыш из ответа FlavorScape в свой ответ. Я не хочу предполагать ваши намерения, но это не совсем справедливо для них. Не могли бы вы откатить ваши изменения? Прямо сейчас это выглядит как плагиат ..
Феликс Ганьон-Гренье
2
Я убрал однострочник, поскольку ответ приведен ниже, однако я видел, что многие популярные ответы обновляют свои ответы, добавляя другие новые ответы, так как большинство читателей не утруждают себя взглядом первых нескольких ответов.
Гарри
130

Быстрый однострочный, линейный способ

 await new Promise(resolve => setTimeout(resolve, 1000));
FlavorScape
источник
4
let sleep = ms => new Promise( r => setTimeout(r, ms));//
однолинейная
8
еще короче :-)await new Promise(resolve => setTimeout(resolve, 5000))
Лиран Бример
1
что это значит, когда вы, ребята, используете «разрешение» х 2 раза в одной строке? Как: ожидание нового обещания (resol => setTimeout (resolv, 1000)); это ссылка себе или как? Я бы сделал что-то вроде этого: function myFunc () {}; ожидание нового обещания (resol => setTimeout (myFunc, 1000));
PabloDK
35

setTimeoutэто не asyncфункция, поэтому вы не можете использовать ее с ES7 async-await. Но вы можете реализовать свою sleepфункцию с помощью ES6 Promise :

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броски ошибки не будут обнаружены.
Берги
@ Берджи Я думаю, что это пузыри до того, new Promiseгде вы можете sleep.catchэто.
Флориан Вендельборн
3
@ Dodekeract Нет, это асинхронный setTimeoutобратный вызов, и new Promiseобратный вызов был сделан долго. Это будет соответствовать глобальному контексту и будет выброшено как необработанное исключение.
Берги
> проблема с отправкой слишком большого количества запросов в секунду. Вы хотите использовать "debounce", возможно, чтобы предотвратить такие вещи, как пользовательский интерфейс, запускающий слишком много руков.
FlavorScape
5

Если вы хотите использовать тот же синтаксис, что и 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 секунд. Не очень ожидаемый пример, ИМО.
mix3d
2
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
Вигнеш
источник
0

Следующий код работает в Chrome и Firefox и, возможно, в других браузерах.

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

Но в Internet Explorer я получаю синтаксическую ошибку для "(resolve **=>** setTimeout..."

Shadowned
источник
0

Сделано Util вдохновленный от Dave «s ответ

В основном передается doneобратный вызов для вызова, когда операция завершена.

// Function to timeout if a request is taking too long
const setAsyncTimeout = (cb, timeout = 0) => new Promise((resolve, reject) => {
  cb(resolve);
  setTimeout(() => reject('Request is taking too long to response'), timeout);
});

Вот как я это использую:

try {
  await setAsyncTimeout(async done => {
    const requestOne = await someService.post(configs);
    const requestTwo = await someService.get(configs);
    const requestThree = await someService.post(configs);
    done();
  }, 5000); // 5 seconds max for this set of operations
}
catch (err) {
  console.error('[Timeout] Unable to complete the operation.', err);
}
Джи Мок
источник
0

Это моя версия с nodejs в 2020 году в AWS Labdas

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)
    ...
}
цвиттерион
источник
-3

Это быстрее исправить в одной строке.

Надеюсь, это поможет.

// WAIT FOR 200 MILISECONDS TO GET DATA //
await setTimeout(()=>{}, 200);
Ромми Гарг
источник
1
Не работает Это: await setTimeout(()=>{console.log('first')}, 200); console.log ('second')печатает второй, затем первый
gregn3
1
@ gregn3 в этом суть да. Это неблокирующее решение, где код вне функции может продолжать выполняться, пока «операция блокировки» завершена вне основного потока программы. Хотя синтаксис, который вы с Ромми и Мохамадом предоставили, не является строго корректным из-за требования о том, что ожидание должно быть вставлено в асинхронную функцию (может быть, это довольно недавнее добавление), также я использую node.js. Это мое подправленное решение. var test = async () => { await setTimeout(()=>{console.log('first')}, 1000); console.log ('second') }Я увеличил время ожидания, чтобы показать его полезность.
Азария