async/await隐式返回Promise?

193

我读到了使用 async 关键字标记的异步函数会隐式返回一个 Promise:

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

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

但这并不连贯...假设doSomethingAsync()返回一个Promise,而await关键字将返回Promise的值,而不是Promise本身,那么我的getVal函数应该返回该值,而不是隐式的Promise。

那么到底是什么情况呢?被async关键字标记的函数是否会隐式返回Promise,还是我们可以控制它们返回什么?

也许如果我们没有显式地返回任何东西,它们就会隐式地返回一个Promise...?

更明确地说,上述情况与下面的情况有所不同:

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

在我的摘要中,这种行为确实与传统的返回语句不一致。当您从async函数明确返回非Promise值时,它似乎会强制将其包装成Promise。

我对此没有太大问题,但它确实违反了正常的JS行为。


1
console.log 显示什么? - Barmar
1
可能 await 会解开 Promise 的结果。 - Hamlet Hakobyan
实际上,我错了,它记录了一个 Promise。 - Alexander Mills
3
JavaScript的Promise试图模仿C#的async await行为。然而,历史上在C#中有很多结构来支持这一点,在JavaScript中则没有。因此,虽然在许多用例中它可能看起来非常相似,但这有点误导人。 - Travis J
1
没错,只是有点令人困惑,因为它是隐式的...即使没有返回语句,它仍然返回一个 Promise...明白吗? - Alexander Mills
显示剩余5条评论
5个回答

230

返回值将始终是一个Promise。如果您没有显式地返回一个Promise,那么您返回的值将自动包装在一个Promise中。

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

即使没有返回值,情况仍然相同!(Promise { undefined }被返回)
async function increment(num) {}

即使有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));

承诺自动解封,因此如果您从async函数中返回一个值的承诺,您将收到该值的承诺(而不是该值的承诺)。请注意保留HTML标记。
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));

在我的概述中,这种行为确实与传统的返回语句不一致。似乎当您从异步函数中显式返回非 Promise 值时,它会强制将其包装在 Promise 中。我对此并不是很介意,但它确实违反了正常的 JS 行为。

ES6 有一些函数不会像 return 一样返回相同的值,这些函数被称为生成器。

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

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

// Logs 'test'.
console.log(foo().next().value);

10
通过静态方法 Promise.resolve,你返回的值将自动被包装在一个 Promise 中,也就是说,如果一个异步函数的返回语句是 - return x; 那么它会隐式地变成 - return Promise.resolve(x); - adnan2nd
仅返回自动生成的 Promise 而不是显式创建它被认为是一种不好的做法吗?在许多情况下,我喜欢这种简洁的方法。 - marlar
1
不,我不认为依赖自动生成的Promise是一种不好的做法。我认为异步函数的一个预期后果是允许您将其管道传输到其他返回Promise的函数中,而无需在代码中出现“Promise”。 例如:async function myFunc() { const val1 = await otherAsyncFunc1(); const val2 = await otherAsyncFunc1(); return val1 + val 2; }async function main() { const result = await myFunc(): console.log("The result is " + result"); } - RonJRH

37

是的,异步函数总是会返回一个承诺(Promise)。

根据 tc39 规范,一个 async function 会被转换为一个生成器并产出 Promise

具体来说:

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

转化为:

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

spawn是一个调用以下算法的函数:

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

1
简而言之,异步函数会被转换成生成器,该生成器会产生 Promise。 我认为您可能将 async functionasync function* 混淆了。前者只是返回一个 Promise,而后者则返回一个生成器,该生成器会产生 Promise。 - cdhowie
5
这个答案主要参考了规范,在审核后,我认为没有任何混淆。的确,异步函数返回 Promise,但是为了实现这一点,它们会被解析成生成器,这些生成器会产生 Promise。 - Jon Surrell

26

你的问题是:如果我创建一个async函数,它应该返回一个promise吗?回答:只要做任何你想做的事情,JavaScript会为你解决。

假设doSomethingAsync是一个返回promise的函数。那么

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

与...完全相同

async function getVal(){
    return doSomethingAsync();
}
你可能会想:“这两者怎么可能相同?”并且你是正确的。如果必要,async 将使用 Promise 神奇地 包装一个值。

更奇怪的是,doSomethingAsync 可以被编写成有时返回 Promise,有时不返回 Promise。尽管如此,这两个函数完全相同,因为 await 也是神奇的。它将在必要时解包 Promise,但对那些不是 Promise 的东西没有影响。


-3

当您调用函数时,只需在其前面添加await:

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

8
"await" 只能在异步函数中使用。 - Han Van Pham

-10

async没有返回Promise,await关键字等待Promise的解决。async是增强版生成器函数,而await的工作方式有点像yield。

我认为语法(但不确定)是

async function* getVal() {...}

ES2016生成器函数工作方式有点类似。我使用tedious编写了一个基于数据库处理程序,可以像这样进行编程:

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

注意我是如何编写普通同步程序的,特别是在 yield connection.execSqlyield connection.callProcedure 处。 db.exec 函数是一个基于 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);
    });
  });
}

6
"async is an enhanced generator function" - 不,它实际上并不是增强版的生成器函数。 - Bergi
如上所述,“异步函数”确实返回一个Promise。至少在概念上,“async”语句的主要作用是将该函数的返回值包装在一个promise中。你甚至可以“await”一个返回Promise的普通旧函数,因为“async function” === “function returning Promise”。 - spechter
3
@bergi,实际上,它是一个增强型生成器函数。它是一个总是返回 Promise 的生成器函数...或者说是其他什么东西。 - Alexander Mills

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接