async / await niejawnie zwraca obietnicę?

111

Czytałem, że funkcje asynchroniczne oznaczone asyncsłowem kluczowym niejawnie zwracają obietnicę:

async function getVal(){
 return await doSomethingAync();
}

var ret = getVal();
console.log(ret);

ale to nie jest spójne ... zakładając, że doSomethingAsync()zwraca obietnicę, a słowo kluczowe await zwróci wartość z obietnicy, a nie z jej samej, wówczas moja funkcja getVal powinna zwrócić tę wartość, a nie ukrytą obietnicę.

Więc o co dokładnie chodzi? Czy funkcje oznaczone słowem kluczowym async niejawnie zwracają obietnice, czy też kontrolujemy to, co zwracają?

Może jeśli nie zwrócimy czegoś jawnie, to pośrednio zwrócą obietnicę ...?

Aby być bardziej zrozumiałym, istnieje różnica między powyższymi a

function doSomethingAync(charlie) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve(charlie || 'yikes');
        }, 100);
    })
}

async function getVal(){
   var val = await doSomethingAync();  // val is not a promise
   console.log(val); // logs 'yikes' or whatever
   return val;  // but this returns a promise
}

var ret = getVal();
console.log(ret);  //logs a promise

W moim streszczeniu zachowanie jest rzeczywiście niezgodne z tradycyjnymi instrukcjami powrotu. Wygląda na to, że gdy jawnie zwrócisz wartość, która nie jest obietnicą z asyncfunkcji, wymusi to zawinięcie jej w obietnicę. Nie mam z tym dużego problemu, ale przeciwstawia się normalnemu JS.

Alexander Mills
źródło
1
Co console.logpokazuje?
Barmar
to wartość przekazywana przez funkcję obietnicy, a nie sama obietnica
Alexander Mills,
Być może oczekiwanie odwija ​​wynik od obietnicy.
Hamlet Hakobyan
właściwie, myliłem się, zapisuje obietnicę
Alexander Mills,
2
Obietnice JavaScript próbują naśladować zachowanie async await w języku c #. Jednak w przeszłości istniało wiele struktur obsługujących to za pomocą języka C #, a żadnej w JavaScript. Więc chociaż w wielu przypadkach może wydawać się bardzo podobny, jest to nieco mylące.
Travis J

Odpowiedzi:

138

Wartość zwracana zawsze będzie obietnicą. Jeśli nie zwrócisz wyraźnie obietnicy, wartość, którą zwrócisz, zostanie automatycznie umieszczona w obietnicy.

async function increment(num) {
  return num + 1;
}

// Even though you returned a number, the value is
// automatically wrapped in a promise, so we call
// `then` on it to access the returned value.
//
// Logs: 4
increment(3).then(num => console.log(num));

To samo, nawet jeśli istnieje await.

function defer(callback) {
  return new Promise(function(resolve) {
    setTimeout(function() {
      resolve(callback());
    }, 1000);
  });
}

async function incrementTwice(num) {
  const numPlus1 = await defer(() => num + 1);
  return numPlus1 + 1;
}

// Logs: 5
incrementTwice(3).then(num => console.log(num));

Promises automatycznie rozpakowuje, więc jeśli zwrócisz obietnicę wartości z asyncfunkcji, otrzymasz obietnicę wartości (nie obietnicę obietnicy wartości).

function defer(callback) {
  return new Promise(function(resolve) {
    setTimeout(function() {
      resolve(callback());
    }, 1000);
  });
}

async function increment(num) {
  // It doesn't matter whether you put an `await` here.
  return defer(() => num + 1);
}

// Logs: 4
increment(3).then(num => console.log(num));

W moim streszczeniu zachowanie jest rzeczywiście niezgodne z tradycyjnymi instrukcjami powrotu. Wygląda na to, że gdy jawnie zwrócisz wartość niezgodną z obietnicą z funkcji asynchronicznej, wymusi to zawinięcie jej w obietnicę. Nie mam z tym dużego problemu, ale przeciwstawia się normalnemu JS.

ES6 ma funkcje, które nie zwracają dokładnie tej samej wartości co return. Te funkcje nazywane są generatorami.

function* foo() {
  return 'test';
}

// Logs an object.
console.log(foo());

// Logs 'test'.
console.log(foo().next().value);
Nathan Wall
źródło
3
„wartość, którą zwracasz, zostanie automatycznie opakowana w obietnicę” za pomocą statycznej metody Promise.resolve, tj. jeśli instrukcja zwrotu funkcji asynchronicznej to - return x; pośrednio staje się - return Promise.resolve (x);
adnan2nd
Czy zwracanie automatycznie utworzonej obietnicy zamiast tworzenia jej samodzielnie jest uważane za złą praktykę? W wielu przypadkach lubię czyste podejście.
marlar
24

Spojrzałem na specyfikację i znalazłem następujące informacje. Krótka wersja jest taka, że async functiondesukrates do generatora, który daje Promises. Więc tak, funkcje asynchroniczne zwracają obietnice .

Zgodnie ze specyfikacją tc39 , następujące są prawdziwe:

async function <name>?<argumentlist><body>

Cukry do:

function <name>?<argumentlist>{ return spawn(function*() <body>, this); }

Gdzie spawn„jest wywołaniem następującego algorytmu”:

function spawn(genF, self) {
    return new Promise(function(resolve, reject) {
        var gen = genF.call(self);
        function step(nextF) {
            var next;
            try {
                next = nextF();
            } catch(e) {
                // finished with failure, reject the promise
                reject(e);
                return;
            }
            if(next.done) {
                // finished with success, resolve the promise
                resolve(next.value);
                return;
            }
            // not finished, chain off the yielded promise and `step` again
            Promise.resolve(next.value).then(function(v) {
                step(function() { return gen.next(v); });
            }, function(e) {
                step(function() { return gen.throw(e); });
            });
        }
        step(function() { return gen.next(undefined); });
    });
}
Jon Surrell
źródło
„Krótka wersja jest taka, że ​​funkcja asynchroniczna desugaruje do generatora, który generuje obietnice”. Myślę, że możesz się mylić async functionz async function*. Ten pierwszy po prostu zwraca obietnicę. Ten ostatni zwraca generator, który daje obietnice.
cdhowie
Ta odpowiedź jest w dużej mierze odniesieniem do specyfikacji i po przeglądzie nie sądzę, aby było jakieś zamieszanie. To prawda, funkcje asynchroniczne zwracają obietnice, ale aby to zrobić, usuwają cukier do generatorów, które dają obietnice.
Jon Surrell
-1

Po prostu dodaj await przed swoją funkcją, gdy ją wywołasz:

var ret = await  getVal();
console.log(ret);
mohsen gharivand
źródło
1
await działa tylko w funkcji async
Han Van Pham
-3

async nie zwraca obietnicy, słowo kluczowe await oczekuje na rozwiązanie obietnicy. async to ulepszona funkcja generatora, która działa trochę jak yield

Myślę, że składnia (nie jestem pewien w 100%) jest

async function* getVal() {...}

Funkcje generatora ES2016 działają trochę tak. Stworzyłem program obsługi bazy danych oparty na żmudnym programie, który programujesz w ten sposób

db.exec(function*(connection) {
  if (params.passwd1 === '') {
    let sql = 'UPDATE People SET UserName = @username WHERE ClinicianID = @clinicianid';
    let request = connection.request(sql);
    request.addParameter('username',db.TYPES.VarChar,params.username);
    request.addParameter('clinicianid',db.TYPES.Int,uid);
    yield connection.execSql();
  } else {
    if (!/^\S{4,}$/.test(params.passwd1)) {
      response.end(JSON.stringify(
        {status: false, passwd1: false,passwd2: true}
      ));
      return;
    }
    let request = connection.request('SetPassword');
    request.addParameter('userID',db.TYPES.Int,uid);
    request.addParameter('username',db.TYPES.NVarChar,params.username);
    request.addParameter('password',db.TYPES.VarChar,params.passwd1);
    yield connection.callProcedure();
  }
  response.end(JSON.stringify({status: true}));

}).catch(err => {
  logger('database',err.message);
  response.end(JSON.stringify({status: false,passwd1: false,passwd2: false}));
});

Zwróć uwagę, jak po prostu programuję to jako normalną synchroniczną, szczególnie w

yield connection.execSql i o godz yield connection.callProcedure

Funkcja db.exec jest dość typowym generatorem opartym na Promise

exec(generator) {
  var self = this;
  var it;
  return new Promise((accept,reject) => {
    var myConnection;
    var onResult = lastPromiseResult => {
      var obj = it.next(lastPromiseResult);
      if (!obj.done) {
        obj.value.then(onResult,reject);
      } else {
       if (myConnection) {
          myConnection.release();
        }
        accept(obj.value);
      }
    };
    self._connection().then(connection => {
      myConnection = connection;
      it = generator(connection); //This passes it into the generator
      onResult();  //starts the generator
    }).catch(error => {
      reject(error);
    });
  });
}
akc42
źródło
4
asynchronizacja to ulepszona funkcja generatora ” - nie, naprawdę nie jest.
Bergi
Jak wspomniano powyżej - „funkcje asynchroniczne” rzeczywiście zwracają obietnicę. Przynajmniej koncepcyjnie głównym celem instrukcji „async” jest zawinięcie wartości zwracanych przez tę funkcję w obietnicę. Możesz nawet „czekać” na zwykłą starą funkcję, która zwraca Obietnicę, i wszystko działa, ponieważ „funkcja asynchroniczna” === „funkcja zwracająca Obietnicę”.
przemówienie
2
@bergi, właściwie jest to ulepszona funkcja generatora. funkcja generatora, która zawsze zwraca obietnicę ... czy coś.
Alexander Mills