我如何从函数 foo 返回一个无同步请求的答案/结果?

我正在尝试从呼叫返回的值,以及将结果分配到函数内部的本地变量,并返回其中一个,但没有这些方式实际上返回答案 - 他们都返回不确定的或无论变量结果的初始值是什么。

一个不同步函数的例子,接受召回(使用 jQuery 的 ajax 函数):

function foo() {
    var result;

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

    return result; // It always returns `undefined`
}

使用 Node.js 的例子:

function foo() {
    var result;

    fs.readFile("path/to/file", function(err, data) {
        result = data;
        // return data; // <- I tried that one as well
    });

    return result; // It always returns `undefined`
}

例如,使用那时承诺的区块:

function foo() {
    var result;

    fetch(url).then(function(response) {
        result = response;
        // return response; // <- I tried that one as well
    });

    return result; // It always returns `undefined`
}

当前回答

起初,呼叫反馈被用于无同步操作(例如,在XMLHttpRequest API)。现在,像浏览器的Fetch API这样的基于承诺的APIs已成为默认解决方案,并且所有现代浏览器和Node.js(服务器侧)都支持更轻松的async/await合成。

一个常见的场景 - 从服务器中获取 JSON 数据 - 可以看起来如下:

async function fetchResource(url) {
  const res = await fetch(url);
  if (!res.ok) {
    throw new Error(res.statusText);
  }
  return res.json();
}

在另一个功能中使用:

async function doSomething() {
  try {
    const data = await fetchResource("https://example.test/resource/1");
    // ...
  } catch (e) {
    // Handle error
    ...
  }
}

如果你设计一个现代的API,强烈建议你更喜欢基于承诺的风格,而不是呼叫回复,如果你继承了一个基于呼叫回复的API,你可以把它作为承诺:

function sleep(timeout) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, timeout);
  });
}

async function fetchAfterTwoSeconds(url) {
  await sleep(2000);
  return fetchResource(url);
}

在Node.js中,历史上仅依赖于呼叫反馈,这种技术是如此常见,以至于它们添加了一个名为util.promisify的辅助功能。

其他回答

如果您不使用 jQuery 在您的代码中,这个答案是为您

function foo() {
    var httpRequest = new XMLHttpRequest();
    httpRequest.open('GET', "/echo/json");
    httpRequest.send();
    return httpRequest.responseText;
}

var result = foo(); // Always ends up being 'undefined'

(注意,对于使用新的Fetch API、Angular 或承诺的人,我在下面添加了另一个答案)


你所面临的

这里有一个简单的模拟:

function getFive(){
    var a;
    setTimeout(function(){
         a=5;
    },10);
    return a;
}

(重定向)

function onComplete(a){ // When the code completes, do this
    alert(a);
}

function getFive(whenDone){
    var a;
    setTimeout(function(){
         a=5;
         whenDone(a);
    },10);
}

使用将是:

getFive(onComplete);

同步AJAX - 不要这样做!!!

var request = new XMLHttpRequest();
request.open('GET', 'yourURL', false);  // `false` makes the request synchronous
request.send(null);

if (request.status === 200) {// That's HTTP for 'ok'
  console.log(request.responseText);
}

二、修复代码

如:

var result = foo();
// Code that depends on `result` goes here

变成:

foo(function(result) {
    // Code that depends on `result`
});

function myHandler(result) {
    // Code that depends on `result`
}
foo(myHandler);

function foo(callback) {
    var httpRequest = new XMLHttpRequest();
    httpRequest.onload = function(){ // When the request is loaded
       callback(httpRequest.responseText);// We're calling our method
    };
    httpRequest.open('GET', "/echo/json");
    httpRequest.send();
}

我们现在已经使我们的 foo 函数接受一个行动运行,当 AJAX 成功完成时,我们可以通过检查答案状态是否不为 200 进行进一步扩展,并按照此进行操作(创建一个错误处理器等)。

如果你仍然很难理解这一点,请阅读MDN的AJAX开始指南。

短答:您的 foo() 方法即时返回,而 $ajax() 通话在函数返回后无同步执行。

也许最简单的方式是将对象转移到 foo() 方法,并在 async 呼叫完成后将结果存储在该对象的一个成员中。

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

请注意,对 foo() 的呼叫仍然不会有用,但是,对 async 呼叫的结果现在将存储在 result.response 中。

var milk = order_milk();
put_in_coffee(milk);

这个问题的经典JavaScript方法,利用JavaScript支持作为可以通过的第一类对象的功能,是将一个功能作为一个参数转移到无同步的请求,它将随后引用,当它在未来完成任务时。

order_milk(put_in_coffee);

order_milk kicks off, orders the milk, then, when and only when it arrives, it invokes put_in_coffee。

order_milk(function(milk) { put_in_coffee(milk, drink_coffee); }

我正在转到把牛奶放进去,以及当牛奶放进去后执行的行动(喝咖啡)。

var answer;
$.ajax('/foo.json') . done(function(response) {
  callback(response.data);
});

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

加入承诺

order_milk() . then(put_in_coffee)

order_milk() . then(put_in_coffee) . then(drink_coffee)

function get_data() {
  return $.ajax('/foo.json');
}

get_data() . then(do_something)

get_data() .
  then(function(data) { console.log(data); });

get_data() .
  then(data => console.log(data));

a();
b();

a() . then(b);

async function morning_routine() {
  var milk   = await order_milk();
  var coffee = await put_in_coffee(milk);
  await drink(coffee);
}

async function foo() {
  data = await get_data();
  console.log(data);
}

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

正如你可以看到的那样:

最简单的:

this.response

e.target.response

例子:

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

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

查看 XMLHttpRequest 先进功能。


现在,如果您想要使用 POST 和 FormData 更复杂的东西,您可以轻松地扩展此功能:

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

使用例子:

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

或者输入完整的表格元素(document.getElementsByTagName('form')[0]):

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

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


正如评论中提到的那样,使用错误&同步完全破坏了答案的点。

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

在第二种情况下,它只是工作,您必须在服务器侧检查是否通过了正确的邮件数据。

未允许的跨域将自动排出错误。


如果你想阻止浏览器一段时间下载一个漂亮的大.txt 文件同步。

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

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


如果你想扩展这个功能......

是的,你可以。

但是,你可以用 XMLHttpRequest 2 做很多事情:

但这里的问题是如何返回一个Ajax答案......(我添加了一个简单的方式)。

我认为,无论使用哪种方法或机制,或者哪种框架(天角/反应)隐藏它,以下原则是:

在程序的流程中(思考代码或甚至最低水平:机器代码),数据可能不会在2秒后,3秒后回来,或者可能根本没有回来,所以没有常见的回来来使用,以便返回数据。