Call An Asynchronous Javascript Function Synchronously

前端 未结 8 1389
南方客
南方客 2020-11-22 17:37

First, this is a very specific case of doing it the wrong way on-purpose to retrofit an asynchronous call into a very synchronous codebase that is many thousands of lines lo

相关标签:
8条回答
  • 2020-11-22 17:44

    Take a look at JQuery Promises:

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

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

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

    Refactor the code:

    
        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);
    
    
    
    0 讨论(0)
  • 2020-11-22 17:45

    "don't tell me about how I should just do it "the right way" or whatever"

    OK. but you should really do it the right way... or whatever

    " I need a concrete example of how to make it block ... WITHOUT freezing the UI. If such a thing is possible in JS."

    No, it is impossible to block the running JavaScript without blocking the UI.

    Given the lack of information, it's tough to offer a solution, but one option may be to have the calling function do some polling to check a global variable, then have the callback set data to the global.

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

    All of this assumes that you can modify doSomething(). I don't know if that's in the cards.

    If it can be modified, then I don't know why you wouldn't just pass a callback to doSomething() to be called from the other callback, but I better stop before I get into trouble. ;)


    Oh, what the heck. You gave an example that suggests it can be done correctly, so I'm going to show that solution...

    function doSomething( func ) {
    
      function callBack(d) {
        func( d );
      }
    
      myAsynchronousCall(param1, callBack);
    
    }
    
    doSomething(function(data) {
        console.log(data);
    });
    

    Because your example includes a callback that is passed to the async call, the right way would be to pass a function to doSomething() to be invoked from the callback.

    Of course if that's the only thing the callback is doing, you'd just pass func directly...

    myAsynchronousCall(param1, func);
    
    0 讨论(0)
  • 2020-11-22 17:45

    There is one nice workaround at http://taskjs.org/

    It uses generators which are new to javascript. So it's currently not implemented by most browsers. I tested it in firefox, and for me it is nice way to wrap asynchronous function.

    Here is example code from project 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;
    }
    
    0 讨论(0)
  • 2020-11-22 17:46

    You can also convert it into callbacks.

    function thirdPartyFoo(callback) {    
      callback("Hello World");    
    }
    
    function foo() {    
      var fooVariable;
    
      thirdPartyFoo(function(data) {
        fooVariable = data;
      });
    
      return fooVariable;
    }
    
    var temp = foo();  
    console.log(temp);
    
    0 讨论(0)
  • 2020-11-22 17:49

    The idea that you hope to achieve can be made possible if you tweak the requirement a little bit

    The below code is possible if your runtime supports the ES6 specification.

    More about async functions

    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;
    }
    
    0 讨论(0)
  • 2020-11-22 17:59

    You can force asynchronous JavaScript in NodeJS to be synchronous with 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!

    But here's how you can 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;
    

    LIMITATIONS:

    These are both a consequence of how sync-rpc is implemented, which is by abusing require('child_process').spawnSync:

    1. This will not work in the browser.
    2. The arguments to your function must be serializable. Your arguments will pass in and out of JSON.stringify, so functions and non-enumerable properties like prototype chains will be lost.
    0 讨论(0)
提交回复
热议问题