async / await retorna implicitamente a promessa?

111

Li que as funções assíncronas marcadas pela asyncpalavra - chave retornam implicitamente uma promessa:

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

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

mas isso não é coerente ... supondo que doSomethingAsync()retorne uma promessa, e a palavra-chave await retornará o valor da promessa, não a promessa em si, então minha função getVal deve retornar esse valor, não uma promessa implícita.

Então, qual é exatamente o caso? As funções marcadas pela palavra-chave async retornam promessas implicitamente ou nós controlamos o que elas retornam?

Talvez, se não retornarmos algo explicitamente, eles retornem implicitamente uma promessa ...?

Para ser mais claro, há uma diferença entre o acima e

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

Na minha sinopse, o comportamento é de fato inconsistente com as declarações de retorno tradicionais. Parece que quando você retorna explicitamente um valor não prometido de uma asyncfunção, ele forçará o agrupamento em uma promessa. Eu não tenho um grande problema com isso, mas desafia o JS normal.

Alexander Mills
fonte
1
O que console.logmostra?
Barmar
é o valor passado pela função de resolução de promessa, não a promessa em si
Alexander Mills
Talvez o await desfaça o resultado da promessa.
Hamlet Hakobyan
na verdade, eu estava errado, ele registra uma promessa
Alexander Mills
2
As promessas do JavaScript estão tentando imitar o comportamento de espera assíncrono do c #. No entanto, historicamente, havia muita estrutura para oferecer suporte a isso com c #, e nenhuma em JavaScript. Portanto, embora em muitos casos de uso possa parecer muito semelhante, é um nome impróprio.
Travis J

Respostas:

137

O valor de retorno sempre será uma promessa. Se você não retornar uma promessa explicitamente, o valor retornado será automaticamente incluído em uma promessa.

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

A mesma coisa, mesmo se houver um 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));

As promessas são desembrulhadas automaticamente, portanto, se você retornar uma promessa para um valor de dentro de uma asyncfunção, receberá uma promessa para o valor (não uma promessa para uma promessa para o valor).

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

Na minha sinopse, o comportamento é de fato inconsistente com as declarações de retorno tradicionais. Parece que quando você retorna explicitamente um valor não prometido de uma função assíncrona, ele forçará o agrupamento em uma promessa. Eu não tenho um grande problema com isso, mas desafia o JS normal.

ES6 tem funções que não retornam exatamente o mesmo valor que o return. Essas funções são chamadas de geradores.

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

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

// Logs 'test'.
console.log(foo().next().value);
Nathan Wall
fonte
3
"o valor que você retornar será automaticamente envolvido em uma promessa" pelo método estático Promise.resolve, ou seja, se a instrução de retorno de uma função assíncrona for - return x; torna-se implicitamente - return Promise.resolve (x);
adnan2 de
É considerado uma má prática apenas retornar a promessa criada automaticamente em vez de criá-la explicitamente você mesmo? De alguma forma, gosto da abordagem limpa em muitos casos.
março
24

Dei uma olhada nas especificações e encontrei as seguintes informações. A versão curta é que um async functiondesugars para um gerador que produz Promises. Portanto, sim, as funções assíncronas retornam promessas .

De acordo com a especificação tc39 , o seguinte é verdadeiro:

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

Desugars para:

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

Onde spawn"é uma chamada para o seguinte algoritmo":

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
fonte
"A versão resumida é que uma função assíncrona desugars para um gerador que produz Promessas." Eu acho que você pode estar confuso async functioncom async function*. O primeiro simplesmente retorna uma promessa. O último retorna um gerador que rende promessas.
cdhowie
Essa resposta é em grande parte uma referência à especificação e, após análise, não acredito que haja qualquer confusão. É verdade, as funções assíncronas retornam promessas, mas para fazer isso, elas direcionam para geradores que rendem promessas.
Jon Surrell
-1

Basta adicionar await antes de sua função ao chamá-la:

var ret = await  getVal();
console.log(ret);
Mohsen Gharivand
fonte
1
await é válido apenas na função assíncrona
Han Van Pham
-3

async não retorna a promessa, a palavra-chave await aguarda a resolução da promessa. async é uma função de gerador aprimorada e await funciona um pouco como o rendimento

Acho que a sintaxe (não tenho 100% de certeza) é

async function* getVal() {...}

As funções do gerador ES2016 funcionam um pouco assim. Eu fiz um gerenciador de banco de dados baseado no entediante que você programa assim

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

Observe como eu apenas o programo como síncrono normal, especialmente em

yield connection.execSql e em yield connection.callProcedure

A função db.exec é um gerador baseado em Promise bastante típico

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
fonte
4
" async é uma função de gerador aprimorada " - não, realmente não é.
Bergi
Como afirmado acima - 'funções assíncronas' realmente retornam uma promessa. Pelo menos conceitualmente, o ponto principal da instrução 'assíncrona' é envolver os valores de retorno dessa função em uma promessa. Você pode até 'aguardar' em uma função simples e antiga que retorna uma promessa, e tudo funciona, porque 'função assíncrona' === 'função retornando promessa'.
espectro de
2
@bergi, na verdade, é uma função de gerador aprimorada. uma função geradora que sempre retorna uma promessa .. ou algo assim.
Alexander Mills