同步调用异步Javascript函数

Rob*_*rth 198 javascript asynchronous

首先,这是一个非常具体的例子,它将错误的方式用于将异步调用改造成一个非常同步的代码库,该代码库有数千行,而且时间目前还没有能力对"执行"进行更改对的." 它伤害了我生命中的每一根纤维,但现实和理想往往不会啮合.我知道这很糟糕.

好的,那个方法,我怎么做到这样我可以:

function doSomething() {

  var data;

  function callBack(d) {
    data = d;
  }

  myAsynchronousCall(param1, callBack);

  // block here and return data when the callback is finished
  return data;
}
Run Code Online (Sandbox Code Playgroud)

示例(或缺少)都使用库和/或编译器,这两者对于该解决方案都不可行.我需要一个如何使其阻塞的具体示例(例如,在调用回调之前不要离开doSomething函数)不要冻结UI.如果在JS中可以做到这样的话.

小智 128

"不要告诉我应该怎么做"正确的方式"或其他什么"

好.但你应该以正确的方式做到......或者其他什么

"我需要一个具体的例子来说明如何阻止它......没有冻结UI.如果在JS中可以做到这样的话."

不,在不阻止UI的情况下阻止正在运行的JavaScript是不可能的.

由于缺乏信息,很难提供解决方案,但一种选择可能是让调用函数进行一些轮询以检查全局变量,然后将回调设置data为全局变量.

function doSomething() {

      // callback sets the received data to a global var
  function callBack(d) {
      window.data = d;
  }
      // start the async
  myAsynchronousCall(param1, callBack);

}

  // start the function
doSomething();

  // make sure the global is clear
window.data = null

  // start polling at an interval until the data is found at the global
var intvl = setInterval(function() {
    if (window.data) { 
        clearInterval(intvl);
        console.log(data);
    }
}, 100);
Run Code Online (Sandbox Code Playgroud)

所有这些都假定您可以修改doSomething().我不知道那是不是卡片.

如果它可以修改,那么我不知道为什么你不会只是通过回调来doSomething()从另一个回调调用,但我最好在遇到麻烦之前停止.;)


哦,到底是什么.您举了一个示例,表明它可以正确完成,所以我将展示该解决方案......

function doSomething( func ) {

  function callBack(d) {
    func( d );
  }

  myAsynchronousCall(param1, callBack);

}

doSomething(function(data) {
    console.log(data);
});
Run Code Online (Sandbox Code Playgroud)

因为您的示例包含传递给异步调用的回调,所以正确的方法是将函数传递doSomething()给要从回调调用.

当然如果这是回调唯一做的事情,你只需func直接传递......

myAsynchronousCall(param1, func);
Run Code Online (Sandbox Code Playgroud)

  • 是的,我知道如何正确地做到这一点,我需要知道如何/如果由于所述的具体原因可以做错.关键是我不想离开doSomething(),直到myAsynchronousCall完成对回调函数的调用.Bleh,它无法完成,正如我所怀疑的那样,我只需要收集互联网的智慧来支持我.谢谢.:-) (18认同)
  • @ RobertC.Barth:是的,不幸的是您的怀疑是正确的。 (2认同)
  • 我总是遇到的问题是“doSomething()”通常是整个程序。用OP的话来说,期望理论编程能够反映现实是徒劳的。 (2认同)

Joh*_*ohn 51

异步功能ES2017中的一项功能,它通过使用promises(特定形式的异步代码)和await关键字使异步代码看起来同步.另请注意下面的代码示例中关键字async前面的function关键字,表示async/await函数.如果await没有使用关键字预先修复的功能,关键字将无法使用async.由于目前没有例外,这意味着没有顶级等待可以工作(顶级等待意味着等待任何功能之外).虽然有一个顶级await提案.

ES2017于2017年6月27日被批准(即最终确定)作为JavaScript的标准.异步等待可能已经在您的浏览器中工作,但如果没有,您仍然可以使用像babeltraceur这样的javascript转换器来使用该功能.Chrome 55完全支持异步功能.因此,如果您有更新的浏览器,您可以尝试下面的代码.

有关浏览器兼容性,请参阅kangax的es2017兼容性表.

这是一个示例async await函数调用doAsync,它占用三秒钟的暂停时间,并在每次暂停后从开始时间打印时间差:

function timeoutPromise (time) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(Date.now());
    }, time)
  })
}

function doSomethingAsync () {
  return timeoutPromise(1000);
}

async function doAsync () {
  var start = Date.now(), time;
  console.log(0);
  time = await doSomethingAsync();
  console.log(time - start);
  time = await doSomethingAsync();
  console.log(time - start);
  time = await doSomethingAsync();
  console.log(time - start);
}

doAsync();
Run Code Online (Sandbox Code Playgroud)

当await关键字放在promise值之前(在这种情况下,promise值是函数doSomethingAsync返回的值)await关键字将暂停执行函数调用,但它不会暂停任何其他函数,它将继续执行其他代码,直到promise解决.在promise解析之后,它将解包promise的值,你可以想到await和promise表达式现在被替换为unwrapped值.

因此,由于等待暂停等待然后在执行行的其余部分之前解开一个值,您可以在for循环和内部函数调用中使用它,如下例所示,它收集数组中等待的时间差并打印出数组.

function timeoutPromise (time) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(Date.now());
    }, time)
  })
}

function doSomethingAsync () {
  return timeoutPromise(1000);
}

// this calls each promise returning function one after the other
async function doAsync () {
  var response = [];
  var start = Date.now();
  // each index is a promise returning function
  var promiseFuncs= [doSomethingAsync, doSomethingAsync, doSomethingAsync];
  for(var i = 0; i < promiseFuncs.length; ++i) {
    var promiseFunc = promiseFuncs[i];
    response.push(await promiseFunc() - start);
    console.log(response);
  }
  // do something with response which is an array of values that were from resolved promises.
  return response
}

doAsync().then(function (response) {
  console.log(response)
})
Run Code Online (Sandbox Code Playgroud)

异步函数本身返回一个promise,因此您可以将其用作链接,就像我在上面或在另一个async await函数中一样.

如果你想同时发送请求你可以使用Promise.all,上面的函数会在发送另一个请求之前等待每个响应.

// no change
function timeoutPromise (time) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(Date.now());
    }, time)
  })
}

// no change
function doSomethingAsync () {
  return timeoutPromise(1000);
}

// this function calls the async promise returning functions all at around the same time
async function doAsync () {
  var start = Date.now();
  // we are now using promise all to await all promises to settle
  var responses = await Promise.all([doSomethingAsync(), doSomethingAsync(), doSomethingAsync()]);
  return responses.map(x=>x-start);
}

// no change
doAsync().then(function (response) {
  console.log(response)
})
Run Code Online (Sandbox Code Playgroud)

如果promise可能拒绝你可以将它包装在try catch中或跳过try catch并让错误传播到async/await函数catch调用.你应该注意不要留下未处理的promise错误,特别是在Node.js. 下面是一些展示错误如何工作的示例.

function timeoutReject (time) {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      reject(new Error("OOPS well you got an error at TIMESTAMP: " + Date.now()));
    }, time)
  })
}

function doErrorAsync () {
  return timeoutReject(1000);
}

var log = (...args)=>console.log(...args);
var logErr = (...args)=>console.error(...args);

async function unpropogatedError () {
  // promise is not awaited or returned so it does not propogate the error
  doErrorAsync();
  return "finished unpropogatedError successfully";
}

unpropogatedError().then(log).catch(logErr)

async function handledError () {
  var start = Date.now();
  try {
    console.log((await doErrorAsync()) - start);
    console.log("past error");
  } catch (e) {
    console.log("in catch we handled the error");
  }
  
  return "finished handledError successfully";
}

handledError().then(log).catch(logErr)

// example of how error propogates to chained catch method
async function propogatedError () {
  var start = Date.now();
  var time = await doErrorAsync() - start;
  console.log(time - start);
  return "finished propogatedError successfully";
}

// this is what prints propogatedError's error.
propogatedError().then(log).catch(logErr)
Run Code Online (Sandbox Code Playgroud)

如果你去这里,你可以看到即将推出的ECMAScript版本的完成提案.

可以与ES2015(ES6)一起使用的另一种方法是使用包含发生器功能的特殊功能.生成器函数有一个yield关键字,可用于复制带有周围函数的await关键字.yield关键字和生成器函数更具通用性,可以做更多的事情,就像async await函数那样.如果你想要一台发电机的功能封装,可用于复制异步等待我想看看co.js.顺便说一下co的功能很像async await函数返回一个promise.老实说,虽然此时浏览器兼容性对于生成器函数和异步函数大致相同,因此如果您只想要异步等待功能,则应使用不带co.js的异步函数.

对于IE以外的所有当前主流浏览器(Chrome,Safari和Edge),浏览器支持实际上非常适合Async功能(截至2017年).

  • 我喜欢这个答案 (2认同)
  • 这是一个很好的答案,但是对于原始的海报问题,我认为所做的只是将问题上移了一层。说他把doSomething变成了一个内部等待的异步函数。该函数现在返回一个Promise,并且是异步的,因此无论调用该函数如何,他都必须重新处理相同的问题。 (2认同)
  • @dpwrussell 这是真的,代码库中有大量异步函数和承诺。解决承诺从蔓延到一切的最好方法就是编写同步回调,除非你做一些非常奇怪和有争议的事情,否则无法同步返回异步值 https://twitter.com/sebmarkbage/status/941214259505119232我不推荐。我将在问题的末尾添加一个编辑,以更完整地回答问题,而不仅仅是回答标题。 (2认同)

Mat*_*lor 47

看看JQuery Promises:

http://api.jquery.com/promise/

http://api.jquery.com/jQuery.when/

http://api.jquery.com/deferred.promise/

重构代码:


    var dfd = new jQuery.Deferred();


    function callBack(data) {
       dfd.notify(data);
    }

    // do the async call.
    myAsynchronousCall(param1, callBack);

    function doSomething(data) {
     // do stuff with data...
    }

    $.when(dfd).then(doSomething);


  • 承诺不同步. (9认同)
  • 这是一个代码的情况,给出了一个同步的幻觉,而实际上不是异步? (7认同)
  • 这个答案+1,这是正确的.但是,我会用`dfd.notify(data)`更新到'dfd.resolve(data)` (3认同)
  • 承诺是IMO组织良好的回调:)如果你需要一个异步调用让我们说一些对象初始化,而不是承诺会有所不同. (2认同)

Geo*_*dov 6

http://taskjs.org/有一个很好的解决方法

它使用了javascript新手的生成器.因此目前大多数浏览器都没有实现它.我在firefox中测试过,对我来说这是包装异步函数的好方法.

这是项目GitHub的示例代码

var { Deferred } = task;

spawn(function() {
    out.innerHTML = "reading...\n";
    try {
        var d = yield read("read.html");
        alert(d.responseText.length);
    } catch (e) {
        e.stack.split(/\n/).forEach(function(line) { console.log(line) });
        console.log("");
        out.innerHTML = "error: " + e;
    }

});

function read(url, method) {
    method = method || "GET";
    var xhr = new XMLHttpRequest();
    var deferred = new Deferred();
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            if (xhr.status >= 400) {
                var e = new Error(xhr.statusText);
                e.status = xhr.status;
                deferred.reject(e);
            } else {
                deferred.resolve({
                    responseText: xhr.responseText
                });
            }
        }
    };
    xhr.open(method, url, true);
    xhr.send();
    return deferred.promise;
}
Run Code Online (Sandbox Code Playgroud)


meu*_*rus 6

可以强制 NodeJS 中的异步 JavaScript 与sync-rpc 同步

不过,它肯定会冻结您的用户界面,所以当谈到是否可以采取您需要采取的快捷方式时,我仍然持反对意见。在 JavaScript 中挂起唯一线程是不可能的,即使 NodeJS 有时允许您阻止它。在您的承诺解决之前,任何回调,事件,任何异步都无法处理。因此,除非你的读者有像 OP 这样不可避免的情况(或者,在我的例子中,正在编写一个没有回调、事件等的美化 shell 脚本),不要这样做!

但您可以这样做:

./calling-file.js

var createClient = require('sync-rpc');
var mySynchronousCall = createClient(require.resolve('./my-asynchronous-call'), 'init data');

var param1 = 'test data'
var data = mySynchronousCall(param1);
console.log(data); // prints: received "test data" after "init data"
Run Code Online (Sandbox Code Playgroud)

./my-asynchronous-call.js

function init(initData) {
  return function(param1) {
    // Return a promise here and the resulting rpc client will be synchronous
    return Promise.resolve('received "' + param1 + '" after "' + initData + '"');
  };
}
module.exports = init;
Run Code Online (Sandbox Code Playgroud)

限制:

这些都是sync-rpc实现方式的结果,这是通过滥用require('child_process').spawnSync

  1. 这在浏览器中不起作用。
  2. 函数的参数必须是可序列化的。您的参数将传入和传出JSON.stringify,因此函数和不可枚举的属性(如原型链)将丢失。

  • 这个答案直接解决了问题的核心。我也许可以将其应用到我的具体案例中。 (3认同)

Per*_*me. 6

你想要的现在实际上是可能的。如果您可以在 Service Worker 中运行异步代码,并在 Web Worker 中运行同步代码,那么您可以让 Web Worker 向 Service Worker 发送同步 XHR,当 Service Worker 执行异步操作时,Web Worker 的线程将等待。这不是一个很好的方法,但它可以工作。


Ale*_*nyi 5

let result;
async_function().then(r => result = r);
while (result === undefined) // Wait result from async_function
    require('deasync').sleep(100);
Run Code Online (Sandbox Code Playgroud)

  • 通过额外的支持信息可以改进您的答案。请[编辑]添加更多详细信息,例如引文或文档,以便其他人可以确认您的答案是正确的。您可以[在帮助中心](/help/how-to-answer)找到有关如何写出好的答案的更多信息。 (5认同)
  • 虽然此代码可以回答问题,但提供有关如何和/或为何解决问题的附加上下文将提高​​答案的长期价值。您可以在帮助中心找到有关如何编写良好答案的更多信息:https://stackoverflow.com/help/how-to-answer。祝你好运 (4认同)