【问题标题】:Is there a way to return a value with async/await instead of a Promise ? As a synchronous function do有没有办法用 async/await 而不是 Promise 返回一个值?作为同步函数
【发布时间】:2018-06-11 07:25:51
【问题描述】:

首先我熟悉异步/同步函数的概念。 还有很多与我有关的问题。但我在任何地方都找不到我的答案。

所以问题是: 有没有办法使用 async/await 返回值而不是 Promise ?作为同步函数。

例如:

async doStuff(param) {
  return await new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('doStuff after a while.');
      resolve('mystuffisdone'+param);
    }, 2000);
  });
}

console.log(doStuff('1'));

获取此函数值的唯一方法是使用.then 函数。

doStuff('1').then(response => {
  console.log(response); // output: mystuffisdone1
  doOtherStuffWithMyResponse(response);
  // ...
});

现在,我想要的是:

const one = doStuff('1');
console.log(one) // mystuffisdone1
const two = doStuff('2');
console.log(two) // mystuffisdone2

为了解释我自己,我有一个充满回调的异步库。我可以通过使用 Promises 和 async/await 来伪造同步行为,将这种异步行为转变为同步行为。 但是还有一个问题,到底还是异步的;超出了异步函数的范围。

doStuff('1').then((r) => {console.log(r)};
console.log('Hello wolrd');

这将导致:Hello world 然后mystuffisdone1。这是使用 async/await 函数时的预期行为。但这不是我想要的。

现在我的问题是:有没有办法在没有关键字 async 的情况下执行与 await 相同的操作?使代码同步?如果不可能,为什么?

编辑:

感谢您的所有回答,我认为我的问题对所有人来说都不是显而易见的。为了澄清我的想法,这是我对@Nikita Isaev 回答的评论。

“我明白为什么所有 I/O 操作都是异步完成的;或者并行完成。但我的问题更多的是关于为什么引擎不以异步方式阻塞同步函数的调用者的事实?我的意思是const a = doStuff(...)是一个Promise,我们需要调用.then才能得到这个函数的结果。但是为什么JavaScript或者Node引擎不会阻塞调用者(只是调用的块)。如果可以的话,我们可以const a = doStuff(...),等待并在a 中得到结果,而不阻塞主线程。就像async/await 一样,为什么没有sync/wait 的地方?”

希望现在更清楚,请随时发表评论或提出任何问题:)

编辑2:

答案为什么的所有精度都在接受答案的 cmets 中。

【问题讨论】:

  • async 函数自然返回Promise。您需要做的是在另一个 async 函数中调用所有 async 函数,并在它们返回的 Promises 上调用 await
  • 您无法从异步调用中获得同步结果。你说你熟悉异步/同步函数,但你没有看到异步调用不可能同步结果。想了一会儿,你应该明白为什么这是不可能的
  • 您无法使异步代码同步。由于您现在无法打开包裹,因此将在一周左右交付。你必须等待它;在这两种情况下。
  • return await new Promise(...) 中,await 有点毫无意义;喜欢写return new Promise((resolve, reject) => new Promise(...).then(resolve, reject))。你可以简单地写return new Promise(...)
  • 谢谢大家的回答。我看到不可能做到这一点,我只是想知道为什么这不可能?是的,我的错误,return 语句将在异步函数的末尾返回 promise 的值。

标签: javascript asynchronous synchronous


【解决方案1】:

有一些 hacky 方法可以做想要的事情,但那将是一种反模式。我会尽力解释。回调是 javascript 中的核心概念之一。当您的代码启动时,您可以设置事件监听器、计时器等。您只需告诉引擎安排一些任务:“当 A 发生时,执行 B”。这就是异步。但是回调丑陋且难以调试,这就是引入 promises 和 async-await 的原因。重要的是要理解这只是一个语法糖,在使用 async-await 时,您的代码仍然异步的。由于 javascript 中没有线程,因此等待某些事件触发或某些复杂操作以同步方式完成会阻塞整个应用程序。 UI 或服务器将停止响应任何其他用户交互并继续等待单个事件触发。

真实案例:

示例 1。

假设我们有一个网络用户界面。我们有一个按钮,点击即可从服务器下载最新信息。想象一下我们同步进行。会发生什么?

myButton.onclick = function () {
  const data = loadSomeDataSync(); // 0
  useDataSomehow(data);
}

一切都是同步的,代码是扁平的,我们很高兴。但用户不是。

javascript 进程只能在特定时刻执行一行代码。 用户将无法点击其他按钮,查看任何动画等,应用程序被卡住等待@987654322 @ 完成。即使这持续了 3 秒,这也是一种糟糕的用户体验,您既无法取消,也无法查看进度,也无法执行其他操作。

示例 2。

我们有一个拥有超过 100 万用户的 node.js http 服务器。对于每个用户,我们需要执行一个持续 5 秒的繁重操作并返回结果。我们可以以同步或异步的方式进行。如果我们异步执行会发生什么?

  1. 用户 1 连接
  2. 我们开始为用户 1 执行繁重的操作
  3. 用户 2 连接
  4. 我们返回用户 1 的数据
  5. 我们开始为用户 2 执行繁重的操作

即我们并行尽快做所有事情。现在想象一下我们以同步方式执行繁重的操作。

  1. 用户 1 连接
  2. 我们开始为用户 1 执行繁重的操作,其他人都在等待它完成
  3. 我们返回用户 1 的数据
  4. 用户 2 连接

现在想象一下繁重的操作需要 5 秒才能完成,而我们的服务器处于高负载状态,它有超过 100 万用户。最后一个要等近500万秒,绝对不行。

这就是为什么:

  1. 在浏览器和服务器 API 中,大部分 i/o 操作都是异步的
  2. 开发人员努力使所有繁重的计算都异步,甚至 React 以异步方式呈现。

【讨论】:

  • 是的,这是我从异步函数中理解的。我可以放置一个while循环来获得所需的行为,但这完全是一个坏主意。我只是想知道为什么,异步函数不能是同步函数?
  • 我明白为什么所有的I/O操作都是异步完成的;或并行完成。但我的问题更多是关于为什么引擎不以异步方式阻止同步函数的调用者的事实?我的意思是const a = doStuff(...) 是一个承诺。我们需要调用.then 来获取这个函数的结果。但是为什么 JavaScript 或 Node 引擎不会阻止调用者(只是进行调用的块)。如果这是可能的,我们可以做const a = doStuff(...) 并在a 中得到结果,而不会阻塞主线程。和 async/await 一样,为什么没有 sync/wait 的地方?
  • @Folkvir 针对不同意图的不同概念。 doASync(); doB() - 意思是“立即做A,完成它,在它完成之前什么都不做。然后做B”。 doAAsync(); doB() - 表示“开始做 A。然后立即做 B”。 await doAAsync(); doB(); - 意思是“开始做一个。暂停当前​​范围,您现在可以做其他事情。当 A 完成时,继续执行并执行 B”。
  • 是的,这正是我打算做的! await doAAsync(); doB() 但是没有关键字async,这是不可能的,因为我们必须将它与await 一起使用。所以我的问题是为什么这是不可能的?
  • 这更多是关于异步和同步函数概念的问题,而不是async/await当前的行为。
【解决方案2】:

不,从 promiseasync/await 不会让您从异步代码到同步代码。为什么?因为两者只是对同一事物的不同包装。 Async 函数像 Promise 一样立即返回。

您需要阻止Event Loop 进行下一次通话。简单的 while(!isMyPromiseResolved){} 也不起作用,因为它还会阻止来自 Promise 的回调,因此永远不会设置 isMyPromiseResolved 标志。

但是...有一些方法可以在没有 async/await 的情况下实现您所描述的。例如:

function runSync(value) {

    let isDone = false;
    let result = null;

    runAsync(value)
    .then(res => {
        result = res;
        isDone = true;
    })
    .catch(err => {
        result = err;
        isDone = true;
    })

    //magic happens here
    require('deasync').loopWhile(function(){return !isDone;});

    return result;
}

runAsync = (value) => {

    return new Promise((resolve, reject) => {

        setTimeout(() => {
            // if passed value is 1 then it is a success
            if(value == 1){
                resolve('**success**');
            }else if (value == 2){
                reject('**error**');
            }
        }, 1000);

    });

}

console.log('runSync(2): ', runSync(2));
console.log('runSync(1): ', runSync(1));

  • 选项 2:调用 execFileSync('node yourScript.js') 示例:
const {execFileSync} = require('child_process');
execFileSync('node',['yourScript.js']);

这两种方法都会阻塞用户线程,因此它们只能用于自动化脚本或类似目的。

【讨论】:

    【解决方案3】:

    将外部主体包裹在异步 IIFE 中:

    /**/(async()=>{
    function doStuff(param) { // no need for this one to be async
      return new Promise((resolve, reject) => { // just return the original promise
        setTimeout(() => {
          console.log('doStuff after a while.');
          resolve('mystuffisdone'+param);
        }, 2000);
      });
    }
    
    console.log(await doStuff('1')); // and await instead of .then
    /**/})().then(()=>{}).catch(e=>console.log(e))
    

    doStuff 函数的额外清理并不是绝对必要的——无论哪种方式都可以——但我希望它有助于阐明异步函数和 Promise 之间的关系。重要的部分是将外部主体包装到异步函数中,以在整个程序中获得改进的语义。

    最后的.then.catch 也不是绝对必要的,但这是一种很好的做法。否则,您的错误可能会被吞没,并且任何移植到 Node 的代码都会抱怨未捕获的 Promise 拒绝。

    【讨论】:

    • 所谓的“最终”.then 永远不需要,无论是否严格! - 但是是的,.catch 在某些环境中会阻止警告
    • 我最终可能会发现这是一个坏习惯并停止这样做。我想我开始使用我使用的一个旧的 Promise 库来做这件事,它只是在第一个 .then 之后才调用,但这不是 Promises/A+ 标准行为,所以当我有冲动时,也许我应该开始拍我的手腕
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-09-11
    • 1970-01-01
    • 2020-07-14
    • 2021-06-07
    • 2018-03-27
    • 2017-02-21
    相关资源
    最近更新 更多