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.


„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?


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.


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.


Czy w twoim przykładzie będzie axios.get(url)działał tak, jakby został nazwany jako await axios.get(url)?
PrestonDocks
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.