async/await 隐式返回承诺?

Posted

技术标签:

【中文标题】async/await 隐式返回承诺?【英文标题】:async/await implicitly returns promise? 【发布时间】:2016-05-20 01:13:26 【问题描述】:

我读到async 关键字标记的异步函数隐式返回一个承诺:

async function getVal()
 return await doSomethingAync();


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

但这不连贯……假设doSomethingAsync()返回一个promise,await关键字将返回promise的值,而不是promise itef,那么我的getVal函数应该返回价值,而不是隐含的承诺。

那么究竟是什么情况呢?由 async 关键字标记的函数是隐式返回 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 函数显式返回非承诺值时,它将强制将其包装在承诺中。 我对它没有什么大问题,但它确实违反了普通的 JS。

【问题讨论】:

console.log 显示什么? 这是 promise resolve 函数传递的值,而不是 promise 本身 也许 await 会解开 promise 的结果。 javascript 的承诺试图模仿 c# 的异步等待行为。然而,历史上有很多结构可以用 c# 来支持它,而在 JavaScript 中则没有。因此,虽然在许多用例中它可能看起来非常相似,但它有点用词不当。 是的,这是正确的,只是有点令人困惑,因为它是隐含的......也就是即使没有 return 语句它仍然返回一个承诺......看到了吗? 【参考方案1】:

返回值永远是一个承诺。如果你没有显式返回一个 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));

Promises 自动解包,因此如果您确实从 async 函数中返回一个值的承诺,您将收到该值的承诺(而不是该值的承诺)。

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

在我的概要中,这种行为确实与传统的不一致 返回语句。看来,当您明确返回 来自异步函数的非承诺值,它将强制将其包装在 承诺。我对它没有什么大问题,但它确实违反了正常 JS。

ES6 的函数返回的值与return 不完全相同。这些函数称为生成器。

function* foo() 
  return 'test';


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

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

【讨论】:

“你返回的值将自动被包装在一个promise中”,通过静态方法Promise.resolve,即如果一个异步函数的return语句是-return x;它隐式变为 - return Promise.resolve(x); 仅仅返回自动创建的promise而不是自己显式创建它被认为是不好的做法吗?不知何故,我在很多情况下都喜欢这种干净的方法。 不,我不认为依赖自动创建的承诺是不好的做法。我认为异步函数的预期结果是允许您通过管道传递到其他返回承诺的函数,而不必在代码中出现“承诺”。例如function async myFunc() const val1 = await otherAsyncFunc1(); const val2 = 等待 otherAsyncFunc1();返回 val1 + val 2; function async main() const result = await myFunc(): console.log("The result is " + result"); 【参考方案2】:

async 不返回承诺,await 关键字等待承诺的解决。 async 是一个增强的生成器函数,await 的工作方式有点像 yield

我认为语法(我不是 100% 确定)是

async function* getVal() ...

ES2016 生成器函数的工作方式有点像这样。我已经制作了一个基于繁琐的数据库处理程序,您可以像这样编程

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 (!/^\S4,$/.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);
    );
  );

【讨论】:

"async 是一个增强的生成器函数" - 不,它确实不是。 如上所述——“异步函数”确实返回了一个 Promise。至少从概念上讲,'async' 语句的要点是将该函数的返回值包装在一个 Promise 中。您甚至可以在返回 Promise 的普通旧函数上“等待”,并且一切正常,因为 'async function' === 'function returned Promise'。 @bergi,实际上,它是一个增强的生成器功能。一个生成器函数,它总是返回一个 promise .. 或其他东西。【参考方案3】:

我查看了规范并找到了以下信息。简短的版本是 async function 去糖到生成 Promises 的生成器。所以,是的,异步函数返回承诺

根据tc39 spec,以下情况属实:

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

【讨论】:

“简短的版本是异步函数对生成 Promises 的生成器进行去糖。” 我认为您可能会将 async functionasync function* 混淆。前者只是返回一个承诺。后者返回一个产生承诺的生成器。 这个答案主要是对规范的参考,经过审查,我认为没有任何混淆。确实,异步函数返回 Promise,但为了做到这一点,它们对产生 Promise 的生成器进行脱糖。【参考方案4】:

调用时只需在函数前添加等待:

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

【讨论】:

await 只在异步函数中有效【参考方案5】:

您的问题是:如果我创建一个async 函数,它是否应该返回一个承诺? 答案:随心所欲,Javascript 会为您解决问题。

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

async function getVal()
    return await doSomethingAsync();

完全一样

async function getVal()
    return doSomethingAsync();

你可能在想“WTF,这些怎么可能是一样的?”,你是对的。 async神奇地用 Promise 包装一个值如果需要

更奇怪的是,doSomethingAsync 可以写成有时返回一个承诺,有时返回一个承诺。这两个功能仍然完全相同,因为await 也是ma​​gic如有必要,它将打开一个 Promise,但它不会对不是 Promise 的东西产生影响。

【讨论】:

以上是关于async/await 隐式返回承诺?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Typescript 认为 async/await 返回包装在承诺中的值?

使用带有 async/await 的 mongoose 承诺

如何将 onload 承诺转换为 Async/Await

Nodejs 承诺 async/await 不能正常工作

将 Sequelize Promise 嵌入到 javascript async/await 链中

async/await 在名为“then”的类方法中