Czy użycie async / await wewnątrz nowego konstruktora Promise () jest anty-wzorzec?

92

Używam tej async.eachLimitfunkcji do kontrolowania maksymalnej liczby operacji na raz.

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.
   });
 });
}

Jak widać, nie mogę zadeklarować myFunctionfunkcji jako asynchronicznej, ponieważ nie mam dostępu do wartości wewnątrz drugiego wywołania zwrotnego eachLimitfunkcji.

Alexis Tyler
źródło
„Jak widzisz, nie mogę zadeklarować funkcji myFunction jako asynchronicznej” - czy możesz wyjaśnić więcej?
zerkms
1
Oh Ok przepraszam. Potrzebuję konstruktora, ponieważ potrzebuję async.eachLimit, aby uniknąć jednocześnie więcej niż 500 operacji asynchronicznych. Pobieram i wyodrębniam dane z plików tekstowych i chcę uniknąć wielu operacji asynchronicznych. Po wyodrębnieniu danych muszę zwrócić Promise z danymi i nie będę w stanie zwrócić jej z wywołania zwrotnego async.eachLimit .
1. Dlaczego potrzebujesz czekania? Async jest już mechanizmem kontroli przepływu. 2. Jeśli chcesz używać async.js z obietnicami wewnątrz node.js, spójrz na async-q
slebetman
Aby uniknąć oddzwonienia do piekła, a jeśli coś rzuci się, zewnętrzna obietnica złapie.

Odpowiedzi:

81

Efektywnie korzystasz z obietnic wewnątrz funkcji wykonawczej konstruktora obietnicy, więc jest to wzorzec anty-wzorca konstruktora Promise .

Twój kod jest dobrym przykładem głównego ryzyka: nie propaguje bezpiecznie wszystkich błędów. Przeczytaj, dlaczego tam .

Ponadto użycie async/ awaitmoże sprawić, że te same pułapki będą jeszcze bardziej zaskakujące. Porównać:

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

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

z naiwnym (złym) asyncodpowiednikiem:

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

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

Poszukaj ostatniego w konsoli internetowej przeglądarki.

Pierwsza z nich działa, ponieważ każdy natychmiastowy wyjątek w funkcji wykonawczej konstruktora Promise wygodnie odrzuca nowo skonstruowaną obietnicę (ale wewnątrz każdej .thenjesteś sam).

Drugi nie działa, ponieważ każdy natychmiastowy wyjątek w asyncfunkcji odrzuca niejawną obietnicę zwróconą przez asyncsamą funkcję .

Ponieważ wartość zwracana funkcji wykonawcy konstruktora obietnicy nie jest używana, to zła wiadomość!

Twój kod

Nie ma powodu, dla którego nie możesz zdefiniować myFunctionjako 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.
    });
  });
}

Chociaż po co używać przestarzałych bibliotek kontrolnych współbieżności, skoro tak jest await?

wysięgnik
źródło
12
Nie potrzebujesz return await: return new Promisewystarczy.
lonesomeday
2
Oficjalnie popieram tę odpowiedź, powiedziałbym dokładnie to samo :-)
Bergi
1
@celoxxx Zajrzyj tutaj . Rzeczywiście, nigdy nie powinieneś używać async.js z obietnicami
Bergi
1
@celoxxx Po prostu upuść typy i stanie się zwykłym js. Nie powinieneś używać async.js, ponieważ różne interfejsy - wywołania zwrotne w stylu węzłów vs obietnice - powodują zbyt duże tarcia i prowadzą do niepotrzebnego skomplikowanego i podatnego na błędy kodu.
Bergi
1
Zgadzam się z tobą ... Ale ten kod jest stary, a ja dokonuję refaktoryzacji, aby używać zdarzeń + async.js (aby kontrolować limit asynchronizacji, jeszcze. Jeśli znasz lepszy sposób, powiedz).
16

Zgadzam się z odpowiedziami podanymi powyżej, a mimo to czasami lepiej jest mieć asynchroniczność w obietnicy, zwłaszcza jeśli chcesz połączyć kilka operacji zwracających obietnice i uniknąć then().then()piekła. Rozważałbym użycie czegoś takiego w tej sytuacji:

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. Funkcja przekazana do Promisekonstruktora nie jest asynchroniczna, więc lintery nie wyświetlają błędów.
  2. Wszystkie funkcje asynchroniczne można wywołać w kolejności sekwencyjnej przy użyciu await.
  3. Można dodać błędy niestandardowe, aby zweryfikować wyniki operacji asynchronicznych
  4. W końcu błąd zostaje dobrze wychwycony.

Wadą jest jednak to, że trzeba pamiętać o zakładaniu try/catchi mocowaniu go reject.

Vladyslav Zavalykhatko
źródło
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);                
        }
    })
}

remove await, a async rozwiąże ten problem. ponieważ zastosowałeś obiekt Promise, to wystarczy.

Alain
źródło
Czy w twoim przykładzie będzie axios.get(url)działał tak, jakby został nazwany jako await axios.get(url)?
PrestonDocks