How do I return the response from an asynchronous

2020-01-22 07:31发布

I have a function foo which makes an Ajax request. How can I return the response from foo?

I tried returning the value from the success callback as well as assigning the response to a local variable inside the function and returning that one, but none of those ways actually return the response.

function foo() {
    var result;

    $.ajax({
        url: '...',
        success: function(response) {
            result = response;
            // return response; // <- I tried that one as well
        }
    });

    return result;
}

var result = foo(); // It always ends up being `undefined`.

30条回答
放荡不羁爱自由
2楼-- · 2020-01-22 07:50

XMLHttpRequest 2 (first of all read the answers from Benjamin Gruenbaum & Felix Kling)

If you don't use jQuery and want a nice short XMLHttpRequest 2 which works on the modern browsers and also on the mobile browsers I suggest to use it this way:

function ajax(a, b, c){ // URL, callback, just a placeholder
  c = new XMLHttpRequest;
  c.open('GET', a);
  c.onload = b;
  c.send()
}

As you can see:

  1. It's shorter than all other functions Listed.
  2. The callback is set directly (so no extra unnecessary closures).
  3. It uses the new onload (so you don't have to check for readystate && status)
  4. There are some other situations which I don't remember that make the XMLHttpRequest 1 annoying.

There are two ways to get the response of this Ajax call (three using the XMLHttpRequest var name):

The simplest:

this.response

Or if for some reason you bind() the callback to a class:

e.target.response

Example:

function callback(e){
  console.log(this.response);
}
ajax('URL', callback);

Or (the above one is better anonymous functions are always a problem):

ajax('URL', function(e){console.log(this.response)});

Nothing easier.

Now some people will probably say that it's better to use onreadystatechange or the even the XMLHttpRequest variable name. That's wrong.

Check out XMLHttpRequest advanced features

It supported all *modern browsers. And I can confirm as I'm using this approach since XMLHttpRequest 2 exists. I never had any type of problem on all browsers I use.

onreadystatechange is only useful if you want to get the headers on state 2.

Using the XMLHttpRequest variable name is another big error as you need to execute the callback inside the onload/oreadystatechange closures else you lost it.


Now if you want something more complex using post and FormData you can easily extend this function:

function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val},placeholder
  c = new XMLHttpRequest;
  c.open(e||'get', a);
  c.onload = b;
  c.send(d||null)
}

Again ... it's a very short function, but it does get & post.

Examples of usage:

x(url, callback); // By default it's get so no need to set
x(url, callback, 'post', {'key': 'val'}); // No need to set post data

Or pass a full form element (document.getElementsByTagName('form')[0]):

var fd = new FormData(form);
x(url, callback, 'post', fd);

Or set some custom values:

var fd = new FormData();
fd.append('key', 'val')
x(url, callback, 'post', fd);

As you can see I didn't implement sync... it's a bad thing.

Having said that ... why don't do it the easy way?


As mentioned in the comment the use of error && synchronous does completely break the point of the answer. Which is a nice short way to use Ajax in the proper way?

Error handler

function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val}, placeholder
  c = new XMLHttpRequest;
  c.open(e||'get', a);
  c.onload = b;
  c.onerror = error;
  c.send(d||null)
}

function error(e){
  console.log('--Error--', this.type);
  console.log('this: ', this);
  console.log('Event: ', e)
}
function displayAjax(e){
  console.log(e, this);
}
x('WRONGURL', displayAjax);

In the above script, you have an error handler which is statically defined so it does not compromise the function. The error handler can be used for other functions too.

But to really get out an error the only way is to write a wrong URL in which case every browser throws an error.

Error handlers are maybe useful if you set custom headers, set the responseType to blob array buffer or whatever...

Even if you pass 'POSTAPAPAP' as the method it won't throw an error.

Even if you pass 'fdggdgilfdghfldj' as formdata it won't throw an error.

In the first case the error is inside the displayAjax() under this.statusText as Method not Allowed.

In the second case, it simply works. You have to check at the server side if you passed the right post data.

cross-domain not allowed throws error automatically.

In the error response, there are no error codes.

There is only the this.type which is set to error.

Why add an error handler if you totally have no control over errors? Most of the errors are returned inside this in the callback function displayAjax().

So: No need for error checks if you're able to copy and paste the URL properly. ;)

PS: As the first test I wrote x('x', displayAjax)..., and it totally got a response...??? So I checked the folder where the HTML is located, and there was a file called 'x.xml'. So even if you forget the extension of your file XMLHttpRequest 2 WILL FIND IT. I LOL'd


Read a file synchronous

Don't do that.

If you want to block the browser for a while load a nice big .txt file synchronous.

function omg(a, c){ // URL
  c = new XMLHttpRequest;
  c.open('GET', a, true);
  c.send();
  return c; // Or c.response
}

Now you can do

 var res = omg('thisIsGonnaBlockThePage.txt');

There is no other way to do this in a non-asynchronous way. (Yeah, with setTimeout loop... but seriously?)

Another point is... if you work with APIs or just your own list's files or whatever you always use different functions for each request...

Only if you have a page where you load always the same XML/JSON or whatever you need only one function. In that case, modify a little the Ajax function and replace b with your special function.


The functions above are for basic use.

If you want to EXTEND the function...

Yes, you can.

I'm using a lot of APIs and one of the first functions I integrate into every HTML page is the first Ajax function in this answer, with GET only...

But you can do a lot of stuff with XMLHttpRequest 2:

I made a download manager (using ranges on both sides with resume, filereader, filesystem), various image resizers converters using canvas, populate web SQL databases with base64images and much more... But in these cases you should create a function only for that purpose... sometimes you need a blob, array buffers, you can set headers, override mimetype and there is a lot more...

But the question here is how to return an Ajax response... (I added an easy way.)

查看更多
在下西门庆
3楼-- · 2020-01-22 07:50

You can use this custom library (written using Promise) to make a remote call.

function $http(apiConfig) {
    return new Promise(function (resolve, reject) {
        var client = new XMLHttpRequest();
        client.open(apiConfig.method, apiConfig.url);
        client.send();
        client.onload = function () {
            if (this.status >= 200 && this.status < 300) {
                // Performs the function "resolve" when this.status is equal to 2xx.
                // Your logic here.
                resolve(this.response);
            }
            else {
                // Performs the function "reject" when this.status is different than 2xx.
                reject(this.statusText);
            }
        };
        client.onerror = function () {
            reject(this.statusText);
        };
    });
}

Simple usage example:

$http({
    method: 'get',
    url: 'google.com'
}).then(function(response) {
    console.log(response);
}, function(error) {
    console.log(error)
});
查看更多
兄弟一词,经得起流年.
4楼-- · 2020-01-22 07:50

Short answer: Your foo() method returns immediately, while the $ajax() call executes asynchronously after the function returns. The problem is then how or where to store the results retrieved by the async call once it returns.

Several solutions have been given in this thread. Perhaps the easiest way is to pass an object to the foo() method, and to store the results in a member of that object after the async call completes.

function foo(result) {
    $.ajax({
        url: '...',
        success: function(response) {
            result.response = response;   // Store the async result
        }
    });
}

var result = { response: null };   // Object to hold the async result
foo(result);                       // Returns before the async completes

Note that the call to foo() will still return nothing useful. However, the result of the async call will now be stored in result.response.

查看更多
【Aperson】
5楼-- · 2020-01-22 07:53

Using Promise

The most perfect answer to this question is using Promise.

function ajax(method, url, params) {
  return new Promise(function(resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.onload = function() {
      resolve(this.responseText);
    };
    xhr.onerror = reject;
    xhr.open(method, url);
    xhr.send(params);
  });
}

Usage

ajax("GET", "/test", "acrive=1").then(function(result) {
    // Code depending on result
})
.catch(function() {
    // An error occurred
});

But wait...!

There is a problem with using promises!

Why should we use our own custom Promise?

I was using this solution for a while until I figured out there is an error in old browsers:

Uncaught ReferenceError: Promise is not defined

So i decided to implement my own Promise class for ES3 to below js compilers if its not defined. Just add this code before your main code and then safely use Promise!

if(typeof Promise === "undefined"){
    function _classCallCheck(instance, Constructor) {
        if (!(instance instanceof Constructor)) { 
            throw new TypeError("Cannot call a class as a function"); 
        }
    }
    var Promise = function () {
        function Promise(main) {
            var _this = this;
            _classCallCheck(this, Promise);
            this.value = undefined;
            this.callbacks = [];
            var resolve = function resolve(resolveValue) {
                _this.value = resolveValue;
                _this.triggerCallbacks();
            };
            var reject = function reject(rejectValue) {
                _this.value = rejectValue;
                _this.triggerCallbacks();
            };
            main(resolve, reject);
        }
        Promise.prototype.then = function then(cb) {
            var _this2 = this;
            var next = new Promise(function (resolve) {
                _this2.callbacks.push(function (x) {
                    return resolve(cb(x));
                });
            });
            return next;
        };
        Promise.prototype.catch = function catch_(cb) {
            var _this2 = this;
            var next = new Promise(function (reject) {
                _this2.callbacks.push(function (x) {
                    return reject(cb(x));
                });
            });
            return next;
        };
        Promise.prototype.triggerCallbacks = function triggerCallbacks() {
            var _this3 = this;
            this.callbacks.forEach(function (cb) {
                cb(_this3.value);
            });
        };
        return Promise;
    }();
}
查看更多
混吃等死
6楼-- · 2020-01-22 07:55

While promises and callbacks work fine in many situations, it is a pain in the rear to express something like:

if (!name) {
  name = async1();
}
async2(name);

You'd end up going through async1; check if name is undefined or not and call the callback accordingly.

async1(name, callback) {
  if (name)
    callback(name)
  else {
    doSomething(callback)
  }
}

async1(name, async2)

While it is okay in small examples it gets annoying when you have a lot of similar cases and error handling involved.

Fibers helps in solving the issue.

var Fiber = require('fibers')

function async1(container) {
  var current = Fiber.current
  var result
  doSomething(function(name) {
    result = name
    fiber.run()
  })
  Fiber.yield()
  return result
}

Fiber(function() {
  var name
  if (!name) {
    name = async1()
  }
  async2(name)
  // Make any number of async calls from here
}

You can checkout the project here.

查看更多
冷血范
7楼-- · 2020-01-22 07:56

You are using Ajax incorrectly. The idea is not to have it return anything, but instead hand off the data to something called a callback function, which handles the data.

That is:

function handleData( responseData ) {

    // Do what you want with the data
    console.log(responseData);
}

$.ajax({
    url: "hi.php",
    ...
    success: function ( data, status, XHR ) {
        handleData(data);
    }
});

Returning anything in the submit handler will not do anything. You must instead either hand off the data, or do what you want with it directly inside the success function.

查看更多
登录 后发表回答