有没有办法用async / await而不是Promise返回一个值?作为同步功能吗

时间:2018-01-01 01:03:39

标签: javascript asynchronous synchronous

首先,我熟悉异步/同步功能的概念。 还有很多与我有关的问题。但我无法在任何地方找到答案。

所以问题是:  有没有办法使用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函数时的预期行为。但那不是我想要的。

现在我的问题是:有没有办法在没有关键字异步的情况下做同样的事情?要使代码同步?如果不可能,为什么?

修改

感谢所有人的回答,我认为我的问题并非一蹴而就。要澄清我的想法,这是我对@Nikita Isaev回答的评论。

“我理解为什么所有的I / O操作都是异步完成的;或者是并行完成的。但我的问题更多的是为什么引擎不会以异步方式阻塞同步函数的调用者?我的意思是const a = doStuff(...)是一个Promise。我们需要调用.then来获取此函数的结果。但是为什么JavaScript或Node引擎不会阻塞调用者(只是调用的块)。如果这样是可能的,我们可以做const a = doStuff(...),等待并在a中得到结果而不阻塞主线程。正如async / await那样,为什么没有同步/等待的地方?“

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

编辑2:

答案原因的所有精确性都在接受答案的评论中。

3 个答案:

答案 0 :(得分:1)

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

真实案例:

示例1。

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

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

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

javascript进程只能在特定时刻执行单行代码。用户将无法点击其他按钮,看到任何动画等,应用程序卡在等待{{ 1}}完成。即使这持续3秒,也是一种糟糕的用户体验,你既不能取消也不能看到进展,也不能做其他事情。

示例2。

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

  1. 用户1连接
  2. 我们开始执行用户1的繁重操作
  3. 用户2连接
  4. 我们返回用户1的数据
  5. 我们开始执行用户2的繁重操作
  6. ...
  7. 即我们并行执行 asap 。现在假设我们以同步的方式进行繁重的操作。

    1. 用户1连接
    2. 我们开始执行用户1的繁重操作,其他人都在等待它完成
    3. 我们返回用户1的数据
    4. 用户2连接
    5. ...
    6. 现在假设繁重的操作需要5秒才能完成,而我们的服务器处于高负载状态,它有超过100万用户。最后一个将不得不等待近500万秒,这绝对不行。

      这就是原因:

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

答案 1 :(得分:1)

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

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

但是...有多种方法可以实现您所描述的内容而无需异步/等待。例如:

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

OR

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

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

答案 2 :(得分:0)

将外体包裹在异步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拒绝。