async / await implícitamente devuelve promise?


He leído que las funciones asincrónicas marcadas por la palabra clave async implícitamente devuelven una promesa:

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

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

Pero eso no es coherente...suponiendo que doSomethingAsync()devuelve una promesa, y la palabra clave await devolverá el valor de la promesa, no la promesa itsef, 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 implícitamente promesas o controlamos lo que ¿volver?

Tal vez si no devolvemos explícitamente algo, entonces implícitamente devuelven una promesa...?

Para ser más claro, hay 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 inconsistente con las declaraciones de retorno tradicionales. Parece que cuando devuelve explícitamente un valor no promise de una función async, forzará a envolverlo en una promesa. No tengo un gran problema con él, pero desafía a los JS normales.

Author: Felix Kling, 2016-02-10

3 answers

El valor de retorno siempre será una promesa. Si no devuelve explícitamente una promesa, el valor que devuelve se envolverá 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));

Promete desenvolver automáticamente, por lo que si devuelve una promesa para un valor desde dentro de una función async, recibirá una promesa para el valor (no una promesa para una promesa para 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 inconsistente con tradicional instrucciones return. Parece que cuando devuelve explícitamente un valor no-promesa de una función asincrónica, forzará envolverlo en un promesa. No tengo un gran problema con él, pero desafía lo normal JS.

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

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

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

// Logs 'test'.
console.log(foo().next().value);
 60
Author: Nathan Wall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-09 21:50:10

Eché un vistazo a la especificación y encontré la siguiente información. La versión corta es que un async function desugars a un generador que produce Promises. Entonces, sí, las funciones asincrónicas devuelven promesas.

De acuerdo con la especificación tc39 , lo siguiente es cierto:

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

Desugars a:

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); });
    });
}
 10
Author: Jon Surrell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-09 21:47:19

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 hecho un manejador de base de datos basado en la parte superior de tedioso que programa como este

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 acabo de programarlo como normal síncrono particularmente en

yield connection.execSql y en yield connection.callProcedure

El db.la función 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);
    });
  });
}
 -2
Author: akc42,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-09 21:34:13