async / await renvoie implicitement la promesse?

111

J'ai lu que les fonctions asynchrones marquées par le asyncmot - clé renvoient implicitement une promesse:

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

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

mais ce n'est pas cohérent ... en supposant que doSomethingAsync()retourne une promesse, et le mot clé await retournera la valeur de la promesse, pas la promesse itsef, alors ma fonction getVal devrait renvoyer cette valeur, pas une promesse implicite.

Alors, quel est exactement le cas? Les fonctions marquées par le mot clé async renvoient-elles implicitement des promesses ou contrôlons-nous ce qu'elles renvoient?

Peut-être que si nous ne renvoyons pas explicitement quelque chose, alors ils renvoient implicitement une promesse ...?

Pour être plus clair, il y a une différence entre ce qui précède et

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

Dans mon synopsis, le comportement est en effet incompatible avec les déclarations de retour traditionnelles. Il semble que lorsque vous retournez explicitement une valeur non prometteuse à partir d'une asyncfonction, cela forcera son encapsulation dans une promesse. Je n'ai pas de gros problème avec ça, mais ça défie le JS normal.

Alexander Mills
la source
1
Que console.logmontre-t-il?
Barmar le
c'est la valeur transmise par la fonction de résolution de promesse, pas la promesse elle
Alexander Mills
Attendre peut-être déballe le résultat de la promesse.
Hamlet Hakobyan le
en fait, je me suis trompé, il enregistre une promesse
Alexander Mills
2
Les promesses de JavaScript tentent d'imiter le comportement d'attente asynchrone de c #. Cependant, il y avait beaucoup de structure en place historiquement pour prendre en charge cela avec c #, et aucune en JavaScript. Ainsi, bien que dans de nombreux cas d'utilisation, cela puisse sembler très similaire, c'est un peu un abus de langage.
Travis J

Réponses:

138

La valeur de retour sera toujours une promesse. Si vous ne retournez pas explicitement une promesse, la valeur que vous renvoyez sera automatiquement enveloppée dans une promesse.

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

Même chose même s'il y a un fichier 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));

Les promesses se déroulent automatiquement, donc si vous retournez une promesse pour une valeur à partir d'une asyncfonction, vous recevrez une promesse pour la valeur (pas une promesse pour une promesse pour la valeur).

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

Dans mon synopsis, le comportement est en effet incompatible avec les déclarations de retour traditionnelles. Il semble que lorsque vous retournez explicitement une valeur non prometteuse à partir d'une fonction asynchrone, cela forcera son encapsulation dans une promesse. Je n'ai pas de gros problème avec ça, mais ça défie le JS normal.

ES6 a des fonctions qui ne renvoient pas exactement la même valeur que le return. Ces fonctions sont appelées générateurs.

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

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

// Logs 'test'.
console.log(foo().next().value);
Mur de Nathan
la source
3
"la valeur que vous renvoyez sera automatiquement enveloppée dans une promesse" par la méthode statique Promise.resolve, c'est-à-dire si l'instruction de retour d'une fonction asynchrone est - return x; il devient implicitement - return Promise.resolve (x);
adnan2nd
Est-il considéré comme une mauvaise pratique de simplement renvoyer la promesse créée automatiquement au lieu de la créer explicitement vous-même? D'une manière ou d'une autre, j'aime l'approche propre dans de nombreux cas.
marlar
24

J'ai jeté un coup d'œil aux spécifications et j'ai trouvé les informations suivantes. La version courte est qu'un async functiondesugars à un générateur qui donne Promises. Donc, oui, les fonctions asynchrones renvoient des promesses .

Selon la spécification tc39 , ce qui suit est vrai:

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

Desugars à:

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

spawn"est un appel à l'algorithme suivant":

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
la source
"La version courte est qu'une fonction asynchrone désuète un générateur qui donne des promesses." Je pense que vous pouvez être déroutant async functionavec async function*. Le premier retourne simplement une promesse. Ce dernier renvoie un générateur qui donne des promesses.
cdhowie
Cette réponse est en grande partie une référence à la spécification et, après examen, je ne pense pas qu'il y ait de confusion. C'est vrai, les fonctions asynchrones renvoient des promesses, mais pour ce faire, elles désuettent des générateurs qui donnent des promesses.
Jon Surrell le
-1

Ajoutez simplement wait avant votre fonction lorsque vous l'appelez:

var ret = await  getVal();
console.log(ret);
mohsen gharivand
la source
1
wait n'est valide qu'en fonction asynchrone
Han Van Pham le
-3

async ne renvoie pas la promesse, le mot-clé await attend la résolution de la promesse. async est une fonction de générateur améliorée et await fonctionne un peu comme yield

Je pense que la syntaxe (je ne suis pas sûr à 100%) est

async function* getVal() {...}

Les fonctions du générateur ES2016 fonctionnent un peu comme ça. J'ai créé un gestionnaire de base de données basé sur le plus fastidieux que vous programmez comme ceci

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

Remarquez comment je le programme simplement comme synchrone normal, en particulier à

yield connection.execSql et à yield connection.callProcedure

La fonction db.exec est un générateur assez typique basé sur 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
la source
4
" async est une fonction de générateur améliorée " - non, ce n'est vraiment pas le cas.
Bergi
Comme indiqué ci-dessus, les «fonctions asynchrones» renvoient effectivement une promesse. Conceptuellement du moins, le point principal de l'instruction 'async' est d'encapsuler les valeurs de retour de cette fonction dans une promesse. Vous pouvez même «attendre» sur une vieille fonction simple qui renvoie une promesse, et tout fonctionne, car «fonction async» === «fonction retournant une promesse».
spechter
2
@bergi, en fait, c'est une fonction de générateur améliorée. une fonction de générateur qui renvoie toujours une promesse .. ou quelque chose.
Alexander Mills