如何从异步调用返回响应?

Harry逆天Eva

2020-03-09

我有一个foo发出Ajax请求的函数我如何从中返回响应foo

我尝试从success回调中返回值,以及将响应分配给函数内部的局部变量并返回该局部变量,但这些方法均未真正返回响应。

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`.

第139篇《如何从异步调用返回响应?》来自Winter(https://github.com/aiyld/aiyld.github.io)的站点

9个回答
Sam猪猪 2020.03.09

除了了解代码之外,还有2个概念是理解JS如何处理回调和异步性的关键。(哪怕一个字?)

事件循环和并发模型

您需要注意三件事:队列; 事件循环和堆栈

从广义上来说,事件循环就像项目管理器一样,它一直在侦听任何想要运行的功能,并在队列和堆栈之间进行通信。

while (queue.waitForMessage()) {
   queue.processNextMessage();
}

一旦收到运行某条消息的消息,就会将其添加到队列中。队列是等待执行的事物的列表(例如您的AJAX请求)。像这样想象:

 1. call foo.com/api/bar using foobarFunc
 2. Go perform an infinite loop
 ... and so on

当这些消息之一要执行时,它会从队列中弹出消息并创建一个堆栈,而该堆栈就是JS执行该消息中的指令所需的一切。因此,在我们的示例中,它被告知致电foobarFunc

function foobarFunc (var) {
  console.log(anotherFunction(var));
}

因此,foobarFunc需要执行的任何操作(在我们的示例中anotherFunction)将被压入堆栈。执行,然后被遗忘-事件循环将移至队列中的下一件事(或侦听消息)

这里的关键是执行顺序。那是

什么时候会运行

当您使用AJAX呼叫外部方或运行任何异步代码(例如setTimeout)时,Javascript依赖于响应才能继续。

最大的问题是,它将何时获得响应?答案是我们不知道-事件循环正在等待该消息说“嘿,我快跑”。如果JS只是同步地等待该消息,则您的应用程序将冻结,并且将变得很糟糕。因此,JS在等待消息添加回队列的同时继续执行队列中的下一项。

这就是为什么在异步功能中我们使用了称为callbacks的东西从字面上看,这有点像一个承诺就像我保证在某个时候返回某些内容一样, jQuery使用称为deffered.done deffered.fail和的特定回调deffered.always你可以在这里看到他们

因此,您需要做的是传递一个函数,该函数应在传递给它的数据的某个点执行。

因为回调不是立即执行,而是在以后执行,所以将引用传递给未执行的函数很重要。所以

function foo(bla) {
  console.log(bla)
}

因此,大多数情况下(但并非总是如此),您foo不会foo()

希望这会有所道理。当您遇到这样令人困惑的事情时,我强烈建议您完整阅读文档以至少了解它。这将使您成为更好的开发人员。

null 2020.03.09

使用承诺

这个问题最完美的答案是使用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);
  });
}

用法

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

可是等等...!

使用诺言有问题!

我们为什么要使用自己的自定义Promise?

我一直使用此解决方案一段时间,直到发现旧浏览器中出现错误:

Uncaught ReferenceError: Promise is not defined

因此,如果未定义ES3,我决定为下面的 js编译器实现自己的Promise类只需在您的主要代码之前添加此代码,然后安全地使用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;
    }();
}
木心小哥 2020.03.09

成功使用callback()内部函数foo()以这种方式尝试。它简单易懂。  

var lat = "";
var lon = "";
function callback(data) {
    lat = data.lat;
    lon = data.lon;
}
function getLoc() {
    var url = "http://ip-api.com/json"
    $.getJSON(url, function(data) {
        callback(data);
    });
}

getLoc();
十三Tony伽罗 2020.03.09

ECMAScript 6具有“生成器”,使您可以轻松地以异步样式进行编程。

function* myGenerator() {
    const callback = yield;
    let [response] = yield $.ajax("https://stackoverflow.com", {complete: callback});
    console.log("response is:", response);

    // examples of other things you can do
    yield setTimeout(callback, 1000);
    console.log("it delayed for 1000ms");
    while (response.statusText === "error") {
        [response] = yield* anotherGenerator();
    }
}

要运行以上代码,请执行以下操作:

const gen = myGenerator(); // Create generator
gen.next(); // Start it
gen.next((...args) => gen.next([...args])); // Set its callback function

如果您需要针对不支持ES6的浏览器,则可以通过Babel或闭包编译器运行代码以生成ECMAScript 5。

回调...args被包装在数组中,并在您读取它们时被解构,以便该模式可以处理具有多个参数的回调。例如,使用节点fs

const [err, data] = yield fs.readFile(filePath, "utf-8", callback);
猪猪十三 2020.03.09

简短的答案是,您必须实现这样的回调:

function callback(response) {
    // Here you can do what ever you want with the response object.
    console.log(response);
}

$.ajax({
    url: "...",
    success: callback
});
Gil小哥伽罗 2020.03.09

这是在许多新的JavaScript框架中使用的数据绑定存储概念两种方式对您非常有用的地方之一...

因此,如果您使用的是Angular,React或任何其他以两种方式进行数据绑定存储概念的框架,则此问题仅为您解决,因此,简单来说,您的结果是undefined在第一阶段,因此您已经result = undefined获得了数据,那么一旦获得结果,它就会被更新并分配给您的Ajax调用响应的新值...

但是,如您在此问题中所提出的那样,如何用纯JavaScriptjQuery实现它呢?

You can use a callback, promise and recently observable to handle it for you, for example in promises we have some function like success() or then() which will be executed when your data is ready for you, same with callback or subscribe function on observable.

For example in your case which you are using jQuery, you can do something like this:

$(document).ready(function(){
    function foo() {
        $.ajax({url: "api/data", success: function(data){
            fooDone(data); //after we have data, we pass it to fooDone
        }});
    };

    function fooDone(data) {
        console.log(data); //fooDone has the data and console.log it
    };

    foo(); //call happens here
});

For more information study about promises and observables which are newer ways to do this async stuffs.

GilL 2020.03.09

角度1

对于使用AngularJS的人,可以使用来处理这种情况Promises

在这里

承诺可用于嵌套异步功能,并允许将多个功能链接在一起。

您也可以在这里找到一个很好的解释

下面提到的文档找到示例

  promiseB = promiseA.then(
    function onSuccess(result) {
      return result + 1;
    }
    ,function onError(err) {
      //Handle error
    }
  );

 // promiseB will be resolved immediately after promiseA is resolved 
 // and its value will be the result of promiseA incremented by 1.

Angular2及更高版本

Angular2同看看下面的例子,但其推荐给使用Observables具有Angular2

 search(term: string) {
     return this.http
  .get(`https://api.spotify.com/v1/search?q=${term}&type=artist`)
  .map((response) => response.json())
  .toPromise();

}

你可以这样消耗掉

search() {
    this.searchService.search(this.searchField.value)
      .then((result) => {
    this.result = result.artists.items;
  })
  .catch((error) => console.error(error));
}

此处查看原始帖子。但是Typescript不支持本机es6 Promises,如果要使用它,可能需要插件。

另外,这里是在这里定义的Promise 规范

做个有心人 2020.03.09

我将以恐怖的手绘漫画来回答。第二图像是为什么的原因resultundefined在你的代码示例。

在此处输入图片说明

飞云小胖 2020.03.09

您使用的Ajax错误。这个想法不是让它返回任何东西,而是将数据传递给称为回调函数的东西,该函数处理数据。

那是:

function handleData( responseData ) {

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

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

在提交处理程序中返回任何内容都不会做任何事情。取而代之的是,您必须移交数据,或者直接在成功函数中执行所需的操作。

问题类别

JavaScript Ckeditor Python Webpack TypeScript Vue.js React.js ExpressJS KoaJS CSS Node.js HTML Django 单元测试 PHP Asp.net jQuery Bootstrap IOS Android