首先,这是一个非常具体的情况,故意以错误的方式将异步调用改造到非常同步的代码库中,该代码库有数千行之长,目前时间无法提供进行更改以“正确执行”的能力。它伤害了我身体的每一根纤维,但现实和理想往往不相符。我知道这很糟糕。

好了,我要怎么做才能:

function doSomething() {

  var data;

  function callBack(d) {
    data = d;
  }

  myAsynchronousCall(param1, callBack);

  // block here and return data when the callback is finished
  return data;
}

示例(或缺少示例)都使用库和/或编译器,这两者都不适用于此解决方案。我需要一个具体的例子,如何使它块(例如不离开doSomething函数,直到回调被调用)而不冻结UI。如果这样的事情在JS中是可能的。


"不要告诉我我应该怎么做"正确的方式"之类的"

好的。但你真的应该用正确的方式去做……或者其他

“我需要一个具体的例子来说明如何让它成为块……不冻结用户界面。如果这样的事情在JS是可能的。”

不,如果不阻塞UI,就不可能阻塞正在运行的JavaScript。

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

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

所有这些都假设您可以修改doSomething()。我不知道有没有可能。

如果它可以修改,那么我不知道为什么你不只是传递一个回调到doSomething()从另一个回调调用,但我最好在陷入麻烦之前停止。;)


哦,管他呢。你给出的例子表明它可以正确地完成,所以我将展示这个解决方案……

function doSomething( func ) {

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

  myAsynchronousCall(param1, callBack);

}

doSomething(function(data) {
    console.log(data);
});

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

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

myAsynchronousCall(param1, func);

看看JQuery的承诺:

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



在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;
}

Async functions, a feature in ES2017, make async code look sync by using promises (a particular form of async code) and the await keyword. Also notice in the code examples below the keyword async in front of the function keyword that signifies an async/await function. The await keyword won't work without being in a function pre-fixed with the async keyword. Since currently there is no exception to this that means no top level awaits will work (top level awaits meaning an await outside of any function). Though there is a proposal for top-level await.

ES2017于2017年6月27日被批准(即最终确定)为JavaScript标准。Async await可能已经在你的浏览器中工作了,但如果不是,你仍然可以使用像babel或traceur这样的javascript转译器使用该功能。Chrome 55完全支持异步功能。因此,如果您有一个较新的浏览器,您可以尝试下面的代码。

浏览器兼容性参见kangax的es2017兼容性表。

下面是一个名为doAsync的示例async await函数,它接受三次1秒暂停,并在每次暂停后打印与开始时间的时间差:

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

当await关键字被放置在promise值之前(在这种情况下,promise值是由函数doSomethingAsync返回的值),await关键字将暂停函数调用的执行,但它不会暂停任何其他函数,它将继续执行其他代码,直到promise解决。在promise解决后,它将打开promise的值,您可以认为await和promise表达式现在已被已打开的值所取代。

因此,由于await只是暂停等待,然后在执行行剩余部分之前展开一个值,您可以在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) })

async函数本身返回一个promise,所以你可以像我上面所做的那样,在另一个async await函数中使用它作为链接的promise。

上面的函数会在发送另一个请求之前等待每个响应,如果你想并发发送请求,你可以使用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) })

如果promise可能被拒绝,你可以将它包装在try catch中,或者跳过try catch,让错误传播到async/await函数的catch调用。你应该注意不要让承诺错误未得到处理,尤其是在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)

如果你去这里,你可以看到即将到来的ECMAScript版本的完成建议。

An alternative to this that can be used with just ES2015 (ES6) is to use a special function which wraps a generator function. Generator functions have a yield keyword which may be used to replicate the await keyword with a surrounding function. The yield keyword and generator function are a lot more general purpose and can do many more things then just what the async await function does. If you want a generator function wrapper that can be used to replicate async await I would check out co.js. By the way co's function much like async await functions return a promise. Honestly though at this point browser compatibility is about the same for both generator functions and async functions so if you just want the async await functionality you should use Async functions without co.js. (I recommend just using async/await it's pretty widely supported in most environments that the above strikethrough is supported in.)

目前除了IE之外,所有主流浏览器(Chrome、Safari和Edge)对Async函数的支持实际上都很好(截至2017年)。


如果稍微调整一下需求,您希望实现的想法就可以实现

如果您的运行时支持ES6规范,则可以使用下面的代码。

更多关于异步函数的信息

async function myAsynchronousCall(param1) {
    // logic for myAsynchronous call
    return d;
}

function doSomething() {

  var data = await myAsynchronousCall(param1); //'blocks' here until the async call is finished
  return data;
}

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

It will definitely freeze your UI though, so I'm still a naysayer when it comes to whether what it's possible to take the shortcut you need to take. It's not possible to suspend the One And Only Thread in JavaScript, even if NodeJS lets you block it sometimes. No callbacks, events, anything asynchronous at all will be able to process until your promise resolves. So unless you the reader have an unavoidable situation like the OP (or, in my case, are writing a glorified shell script with no callbacks, events, etc.), DO NOT DO THIS!

但你可以这样做:

。/ 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"

。/ 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;

限制:

这些都是sync-rpc实现的结果,即滥用require('child_process').spawnSync:

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


你也可以把它转换成回调。

function thirdPartyFoo(callback) {    
  callback("Hello World");    
}

function foo() {    
  var fooVariable;

  thirdPartyFoo(function(data) {
    fooVariable = data;
  });

  return fooVariable;
}

var temp = foo();  
console.log(temp);

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


人们可能不会考虑的一件事是:如果你控制了异步函数(其他代码段依赖于它),并且它所采用的代码路径不一定是异步的,你可以通过创建一个可选参数使它成为同步的(而不破坏其他代码段)。

目前:

async function myFunc(args_etcetc) {
    // you wrote this
    return 'stuff';
}

(async function main() {
    var result = await myFunc('argsetcetc');
    console.log('async result:' result);
})()

考虑:

function myFunc(args_etcetc, opts={}) {
    /*
        param opts :: {sync:Boolean} -- whether to return a Promise or not
    */
    var {sync=false} = opts;
    if (sync===true)
        return 'stuff';
    else
        return new Promise((RETURN,REJECT)=> {
            RETURN('stuff');
        });
}


// async code still works just like before:
(async function main() {
    var result = await myFunc('argsetcetc');
    console.log('async result:', result);
})();
// prints: 'stuff'

// new sync code works, if you specify sync mode:
(function main() {
    var result = myFunc('argsetcetc', {sync:true});
    console.log('sync result:', result);
})();
// prints: 'stuff'

当然,如果异步函数依赖于固有的异步操作(网络请求等),这是行不通的,在这种情况下,努力是徒劳的(没有有效地等待无理由的空闲旋转)。

另外,根据传入的选项返回值或Promise也是相当难看的。

("Why would I have written an async function if it didn't use async constructs?" one might ask? Perhaps some modalities/parameters of the function require asynchronicity and others don't, and due to code duplication you wanted a monolithic block rather than separate modular chunks of code in different functions... For example perhaps the argument is either localDatabase (which doesn't require await) or remoteDatabase (which does). Then you could runtime error if you try to do {sync:true} on the remote database. Perhaps this scenario is indicative of another problem, but there you go.)


在Node.js中,可以编写调用异步操作的同步代码。 节点光纤可以做到这一点。它是作为npm模块提供的第三方本地扩展。 它实现了光纤/协程,所以当一个特定的光纤被阻塞等待异步操作时,整个程序事件循环不会阻塞——另一个光纤(如果存在)继续它的工作。

使用纤维,你的代码看起来像这样:

var Fiber = require('fibers');

function doSomething() {
  var fiber = Fiber.current;

  function callBack(data) {
    fiber.run(data);
  }

  myAsynchronousCall(param1, callBack);

  // execution blocks here
  var data = Fiber.yield();
  return data;
}

// The whole program must be wrapped with Fiber
Fiber(function main() {

  var data = doSomething();
  console.log(data);

}).run();

注意,应该避免使用async/await。请看下面来自项目自述文件https://github.com/laverdet/node-fibers:的说明

NOTE OF OBSOLESCENCE -- The author of this project recommends you avoid its use if possible. The original version of this module targeted nodejs v0.1.x in early 2011 when JavaScript on the server looked a lot different. Since then async/await, Promises, and Generators were standardized and the ecosystem as a whole has moved in that direction. I'll continue to support newer versions of nodejs as long as possible but v8 and nodejs are extraordinarily complex and dynamic platforms. It is inevitable that one day this library will abruptly stop working and no one will be able to do anything about it. I'd like to say thank you to all the users of fibers, your support over the years has meant a lot to me.


使用Node 16的工作线程实际上使这成为可能,下面的例子是主线程正在运行异步代码,而工作线程正在同步地等待它。

这并不是说它非常有用,但它至少模糊地完成了同步等待异步代码所提出的最初问题。

const {
    Worker, isMainThread, parentPort, receiveMessageOnPort
} = require('worker_threads');
if (isMainThread) {
    const worker = new Worker(__filename);
    worker.on('message', async () => {
        worker.postMessage(await doAsyncStuff());
    });
} else {
    console.log(doStuffSync());
}

function doStuffSync(){
    parentPort.postMessage({fn: 'doStuff'});
    let message;
    while (!message) {
        message = receiveMessageOnPort(parentPort)
    }
    return message;
}

function doAsyncStuff(){
    return new Promise((resolve) => setTimeout(() => resolve("A test"), 1000));
}

promise的这种能力包括同步操作的两个关键特性,如下所示(or then()接受两个回调)。 当您获得结果时,调用resolve()并传递最终结果。 如果出现错误,调用reject()。

其思想是将结果通过.then()处理程序链传递。

const synchronize = (() => {
    let chain = Promise.resolve()
    return async (promise) => {
        return chain = chain.then(promise)
    }
})()

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