Поєднання функції асинхронізації + wait + setTimeout


305

Я намагаюся використовувати нові функції асинхронізації і сподіваюся, що вирішення моєї проблеми допоможе іншим у майбутньому. Це мій код, який працює:

  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 працює занадто швидко, і сценарій надсилає занадто багато запитів в секунду API google. Тому я хотів би побудувати функцію сну, яка затримує запит. Таким чином, я також міг використовувати цю функцію для затримки інших запитів. Якщо є інший спосіб відкласти запит, повідомте мене про це.

У всякому разі, це мій новий код, який не працює. Відповідь на запит повертається до функції анонімного асинхронізації в межах 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);
  }

Я вже спробував деякі варіанти: збереження відповіді у глобальній змінній та повернення її з функції сну, зворотний виклик у межах анонімної функції тощо.

Відповіді:


614

Ваша 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]являє собою позначення ? Я його раніше не бачив, і гугл важко
natedog


1
@tinkerr " Час очікування потрібно оголосити асинхронним, якщо його потрібно чекати " - Ні. Функції потрібно лише повернути обіцянку, на яку можна чекати (або насправді достатньо допустимого). Оскільки це досягається, це залежить від реалізації функції, вона не повинна бути an async function.
Бергі

2
@naisanza Ні, async/ awaitце засноване на обіцянках. Єдине, що його замінює - це thenдзвінки.
Бергі

150

Оскільки Node 7.6 , ви можете комбінувати функції promisifyфункцій з модуля утиліти 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
Гей, там @Harry. Здається, ви включили один лайнер з відповіді FlavorScape у власну відповідь. Я не хочу припускати ваших намірів, але це не дуже справедливо до них. Чи можете ви відкатати свою редакцію? Зараз це трохи схоже на плагіат ..
Фелікс Ганьон-Греньє,

2
Я видалив однолінійку, оскільки відповідь знаходиться внизу, проте я бачив, що багато популярних відповідей оновлюють свої відповіді, щоб включати інші нові відповіді, оскільки більшість читачів не переймаються першими кількома відповідями.
Гаррі

130

Швидкий однолінійний, лінійний спосіб

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

3
let sleep = ms => new Promise( r => setTimeout(r, ms));// функція одного лайнера
Soldeplata Saketos

8
ще коротше :-)await new Promise(resolve => setTimeout(resolve, 5000))
Ліран Брімер

1
що це означає, коли ви, хлопці, використовуєте "вирішити" х 2 рази в одному рядку? Як: чекайте нової Обіцянки (разрешить => setTimeout (вирішити, 1000)); чи робить це реф. собі чи що? Я б зробив щось подібне замість цього: функцію myFunc () {}; чекайте нової Обіцянки (рішення => 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 / wait з setTimeout, хоча це може не допомогти вирішити вашу проблему з надсиланням занадто багато запитів в секунду.


Оновлення: Сучасні версії node.js мають вбудовану програму асинхронізації асинхронізації, доступну через помічник util.promisify :

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

2
Ви не повинні цього робити, коли fnкидки помилка не буде спіймана.
Бергі

@Bergi Я думаю, що це бульбашки до того місця, 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..."


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

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') }Я продовжив тайм-аут, щоб показати, що це корисність.
азарія
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.