有没有办法 return 使用 async/await 而不是 Promise 的值?作为一个同步函数做

Is there a way to return a value with async/await instead of a Promise ? As a synchronous function do

首先我熟悉asynchronous/synchronous函数的概念。 还有很多和我相关的问题。但我无法在任何地方找到答案。

所以问题是: 有没有办法使用 async/await return 一个值而不是 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 understand why all I/O operations are asynchronously done; or done in parallel. But my question is more about the fact that why the engine doesn't block the caller of the sync function in an asynchronous manner ? I mean const a = doStuff(...) is a Promise. We need to call .then to get the result of this function. But why JavaScript or Node engine does not block the caller (just the block where the call is made). If this is possible, we could do const a = doStuff(...), wait and get the result in a without blocking the main thread. As async/await does, why there is no place for sync/wait ?"

希望现在更清楚了,随时发表评论或提问 :)

编辑 2:

答案的所有精确度都在已接受答案的评论中。

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

真实案例:

示例 1.

假设我们有一个网络 UI。我们有一个按钮,可以在单击时从服务器下载最新信息。想象一下我们同步进行。会发生什么?

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

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

一个 javascript 进程只能在特定时刻执行一行代码。 用户将无法单击其他按钮、查看任何动画等,应用卡住等待 loadSomeDataSync() 完成。即使这持续了 3 秒,这也是一个糟糕的用户体验,你既不能取消,也不能看到进度,也不能做其他事情。

示例 2.

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

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

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

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

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

这就是为什么:

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

在异步 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 函数的额外清理并不是绝对必要的——它可以以任何方式工作——但我希望它有助于阐明异步函数和 Promises 之间的关系。重要的部分是将外部主体包装到异步函数中,以在整个程序中获得改进的语义。

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

不,从 promiseasync/await 不会让您从异步代码转到同步代码。为什么?因为两者只是对同一事物的不同包装。异步函数 returns 就像 promise 一样。

您需要阻止 Event Loop 转到下一个电话。简单的 while(!isMyPromiseResolved){} 也不起作用,因为它还会阻止来自承诺的回调,因此永远不会设置 isMyPromiseResolved 标志。

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

  • 选项 1:使用 deasync approach。示例:
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']);

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