Использование async / await внутри нового конструктора Promise () - это анти-шаблон?


93

Я использую async.eachLimitфункцию для управления максимальным количеством операций за раз.

const { eachLimit } = require("async");

function myFunction() {
 return new Promise(async (resolve, reject) => {
   eachLimit((await getAsyncArray), 500, (item, callback) => {
     // do other things that use native promises.
   }, (error) => {
     if (error) return reject(error);
     // resolve here passing the next value.
   });
 });
}

Как видите, я не могу объявить myFunctionфункцию как асинхронную, потому что у меня нет доступа к значению во втором обратном вызове eachLimitфункции.


«Как видите, я не могу объявить myFunction async» - не могли бы вы уточнить?
zerkms

1
О, хорошо ... извини. Мне нужен конструктор, потому что мне нужен async.eachLimit, чтобы избежать более 500 асинхронных операций за раз. Я загружаю и извлекаю данные из текстовых файлов, и я хочу избежать большого количества асинхронных операций, после извлечения данных я должен вернуть Promise с данными, и я не смогу вернуть его из обратного вызова async.eachLimit .

1. Зачем вам ожидание? Async уже является механизмом потока управления. 2. Если вы хотите использовать async.js с обещаниями внутри node.js, взгляните на async-q
slebetman

Чтобы избежать ада обратных вызовов, и если что-то сработает, внешнее обещание будет поймано.

Ответы:


81

Вы эффективно используете обещания внутри функции-исполнителя конструктора обещаний, поэтому это анти-шаблон конструктора обещаний .

Ваш код является хорошим примером основного риска: небезопасного распространения всех ошибок. Почитай почему там .

Кроме того, использование async/ awaitможет сделать те же самые ловушки еще более удивительными. Сравните:

let p = new Promise(resolve => {
  ""(); // TypeError
  resolve();
});

(async () => {
  await p;
})().catch(e => console.log("Caught: " + e)); // Catches it.

с наивным (неправильным) asyncэквивалентом:

let p = new Promise(async resolve => {
  ""(); // TypeError
  resolve();
});

(async () => {
  await p;
})().catch(e => console.log("Caught: " + e)); // Doesn't catch it!

Найдите последнюю версию в веб-консоли вашего браузера.

Первый работает, потому что любое немедленное исключение в функции-исполнителе конструктора Promise удобно отклоняет вновь созданное обещание (но внутри любого .thenвы сами).

Второй не работает, потому что любое немедленное исключение в asyncфункции отклоняет неявное обещание, возвращаемое самой asyncфункцией .

Поскольку возвращаемое значение функции исполнителя конструктора обещаний не используется, это плохие новости!

Ваш код

Нет причин, по которым вы не можете определить myFunctionкак async:

async function myFunction() {
  let array = await getAsyncArray();
  return new Promise((resolve, reject) => {
    eachLimit(array, 500, (item, callback) => {
      // do other things that use native promises.
    }, error => {
      if (error) return reject(error);
      // resolve here passing the next value.
    });
  });
}

Хотя зачем использовать устаревшие библиотеки управления параллелизмом, если они у вас есть await?


12
Вам не нужно return await: return new Promiseдостаточно.
lonesomeday

2
Я официально одобряю этот ответ, я бы сказал то же самое :-)
Берги

1
@celoxxx Взгляните сюда . Вы действительно никогда не должны использовать async.js с обещаниями
Берги

1
@celoxxx Просто отбросьте типы, и он станет простым js. Вы не должны использовать async.js, потому что разные интерфейсы - обратные вызовы в стиле узла и обещания - вызывают слишком много трений и приводят к ненужному сложному и подверженному ошибкам коду.
Берги

1
Я согласен с вами ... Но этот код старый, и я занимаюсь рефакторингом, чтобы использовать events + async.js (пока что для управления пределом async. Если вы знаете способ получше, скажите, пожалуйста).

16

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

const operation1 = Promise.resolve(5)
const operation2 = Promise.resolve(15)
const publishResult = () => Promise.reject(`Can't publish`)

let p = new Promise((resolve, reject) => {
  (async () => {
    try {
      const op1 = await operation1;
      const op2 = await operation2;

      if (op2 == null) {
         throw new Error('Validation error');
      }

      const res = op1 + op2;
      const result = await publishResult(res);
      resolve(result)
    } catch (err) {
      reject(err)
    }
  })()
});

(async () => {
  await p;
})().catch(e => console.log("Caught: " + e));
  1. Функция, переданная Promiseконструктору, не является асинхронной, поэтому линтеры не показывают ошибок.
  2. Все асинхронные функции можно вызывать в последовательном порядке с помощью await.
  3. Пользовательские ошибки могут быть добавлены для проверки результатов асинхронных операций
  4. В конце концов ошибка ловится красиво.

Однако недостатком является то, что вы должны помнить о том, что try/catchего вставляли и прикрепляли reject.


4
static getPosts(){
    return new Promise( (resolve, reject) =>{
        try {
            const res =  axios.get(url);
            const data = res.data;
            resolve(
                data.map(post => ({
                    ...post,
                    createdAt: new Date(post.createdAt)
                }))
            )
        } catch (err) {
            reject(err);                
        }
    })
}

удалите ожидание, и асинхронный режим решит эту проблему. потому что вы применили объект Promise, этого достаточно.


Итак, в вашем примере он будет axios.get(url)работать так, как если бы он был вызван как await axios.get(url)?
PrestonDocks,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.