async / await devuelve implícitamente la promesa?

111

Leí que las funciones asíncronas marcadas por la asyncpalabra clave devuelven implícitamente una promesa:

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

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

pero eso no es coherente ... asumiendo que doSomethingAsync()devuelve una promesa, y la palabra clave await devolverá el valor de la promesa, no la promesa en sí, entonces mi función getVal debería devolver ese valor, no una promesa implícita.

Entonces, ¿cuál es exactamente el caso? ¿Las funciones marcadas por la palabra clave async devuelven promesas implícitamente o controlamos lo que devuelven?

¿Quizás si no devolvemos algo explícitamente, entonces devuelven implícitamente una promesa ...?

Para ser más claro, existe una diferencia entre lo anterior y

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

En mi sinopsis, el comportamiento es de hecho inconsistente con las declaraciones de devolución tradicionales. Parece que cuando devuelve explícitamente un valor no prometido de una asyncfunción, lo forzará a envolverlo en una promesa. No tengo un gran problema con él, pero desafía al JS normal.

Alexander Mills
fuente
1
¿Qué console.logmuestra?
Barmar
es el valor pasado por la función de resolución de la promesa, no la promesa en sí misma
Alexander Mills
Quizás aguardar desenvuelve el resultado de la promesa.
Hamlet Hakobyan
en realidad, estaba equivocado, registra una promesa
Alexander Mills
2
Las promesas de JavaScript están tratando de imitar el comportamiento de espera asíncrono de c #. Sin embargo, históricamente hubo mucha estructura para soportar eso con c #, y ninguna en JavaScript. Entonces, si bien en muchos casos de uso puede parecer muy similar, es un nombre poco apropiado.
Travis J

Respuestas:

138

El valor de retorno siempre será una promesa. Si no devuelve explícitamente una promesa, el valor que devuelva se incluirá automáticamente en una promesa.

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

Lo mismo incluso si hay un 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));

Promesas que se desenvuelven automáticamente, por lo que si devuelve una promesa por un valor desde dentro de una asyncfunción, recibirá una promesa por el valor (no una promesa por una promesa por el 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));

En mi sinopsis, el comportamiento es de hecho inconsistente con las declaraciones de devolución tradicionales. Parece que cuando devuelve explícitamente un valor que no es de promesa de una función asincrónica, lo forzará a envolverlo en una promesa. No tengo un gran problema con él, pero desafía al JS normal.

ES6 tiene funciones que no devuelven exactamente el mismo valor que el return. Estas funciones se denominan generadores.

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

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

// Logs 'test'.
console.log(foo().next().value);
Nathan Wall
fuente
3
"el valor que devuelva se incluirá automáticamente en una promesa" mediante el método estático Promise.resolve, es decir, si la declaración de retorno de una función asíncrona es - return x; implícitamente se convierte en - return Promise.resolve (x);
adnan 2 de
¿Se considera una mala práctica devolver la promesa creada automáticamente en lugar de crearla explícitamente usted mismo? De alguna manera me gusta el enfoque limpio en muchos casos.
marlar
24

Eché un vistazo a la especificación y encontré la siguiente información. La versión corta es que an async functiondesugars a un generador que produce Promises. Entonces, sí, las funciones asíncronas devuelven promesas .

Según la especificación tc39 , se cumple lo siguiente:

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

Azúcares para:

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

Donde spawn"es una llamada al siguiente 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
fuente
"La versión corta es que una función asíncrona desazúcar a un generador que produce Promesas". Creo que puedes confundirte async functioncon async function*. El primero simplemente devuelve una promesa. Este último devuelve un generador que rinde promesas.
cdhowie
Esta respuesta es en gran parte una referencia a la especificación y, después de la revisión, no creo que haya ninguna confusión. Es cierto, las funciones asíncronas devuelven promesas, pero para hacer esto, desugar a los generadores que producen promesas.
Jon Surrell
-1

Simplemente agregue await antes de su función cuando la llame:

var ret = await  getVal();
console.log(ret);
Mohsen Gharivand
fuente
1
await solo es válido en función asincrónica
Han Van Pham
-3

async no devuelve la promesa, la palabra clave await espera la resolución de la promesa. async es una función de generador mejorada y await funciona un poco como yield

Creo que la sintaxis (no estoy 100% seguro) es

async function* getVal() {...}

Las funciones del generador ES2016 funcionan un poco así. He creado un controlador de base de datos basado en tedioso que programa así

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 cómo lo programo como sincrónico normal, particularmente en

yield connection.execSql y en yield connection.callProcedure

La función db.exec es un generador basado en Promesas 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
fuente
4
" async es una función de generador mejorada " - no, realmente no lo es.
Bergi
Como se indicó anteriormente, las 'funciones asíncronas' de hecho devuelven una Promesa. Al menos conceptualmente, el punto principal de la declaración 'async' es envolver los valores de retorno de esa función en una promesa. Incluso puede 'esperar' en una función vieja y simple que devuelve una Promesa, y todo funciona, porque 'función asíncrona' === 'función que devuelve Promesa'.
spechter
2
@bergi, en realidad, es una función de generador mejorada. una función generadora que siempre devuelve una promesa ... o algo.
Alexander Mills