【问题标题】:Execute code after data from asynchronous XMLHttpRequest is available在异步 XMLHttpRequest 的数据可用后执行代码
【发布时间】:2016-07-13 21:23:49
【问题描述】:

我需要从服务器加载一个 JSON 文件,稍后我将使用它来填充网站上的内容。使用XMLHttpRequest 加载 JSON 文件应该是异步的,并且可以尽快发生。然而,填充内容需要加载 DOM。

现在,我调用xhr.open('GET', 'records.json', false) 使请求同步。当加载和解析 JSON 所花费的时间比浏览器加载 DOM 所花费的时间更多时,这是必要的。当这种情况发生时(即连接速度较慢),DOM 将被加载,DOMContentLoaded 事件监听器中的代码将在 listData 上执行,undefined 仍然是 undefined。不好。

如何使用异步XMLHttpRequest 并同时在DOMContentLoaded 上执行我的代码?我想在 listData 完全加载(即 JSON.parse() 完成)并且 DOM 可用后立即执行我的代码。当这两个条件都满足时,我就可以走了。

function thisFunctionIsCalledFromTheHtml() {
    // Get the JSON data by using a XML http request
    var listData;
    var xhr = new XMLHttpRequest();
    // The request needs to be synchronous for now because on slow connections the DOM is ready
    // before it fetches everything from the json file
    xhr.open('GET', 'records.json', false);
    xhr.addEventListener("load", function() {
      if (xhr.readyState === 4) {
          if (xhr.status === 200) {
              listData = JSON.parse(xhr.responseText);
          } else {
              console.error('Error: ' + xhr.status + ' ' + xhr.statusText);
          }
      }
    });
    xhr.addEventListener("error", function() {
      console.error('Error: ' + xhr.status + ' ' + xhr.statusText);
    });
    xhr.send(null);

    document.addEventListener("DOMContentLoaded", function() {
        var placeholderKeys = [];
        for (var key in listData) {
            var value = listData[key];
            placeholderKeys = placeholderKeys.concat(value.title, value.abbr, value.keywords);
        }

        var filterInput = document.getElementById('input-id');
        filterInput.placeholder = placeholderKeys[
            Math.floor(Math.random() * placeholderKeys.length)
        ];
    });
}

【问题讨论】:

  • 如果您确保从放置在元素之后的脚本标记调用此函数,则不需要DOMContentLoaded
  • @charlietfl 但我不想等到加载 DOM 之后再加载 JSON。这应该尽快发生。
  • 替代方案是返回一个承诺并将承诺回调放入DOMContentLoaded

标签: javascript json asynchronous xmlhttprequest


【解决方案1】:

您可以在异步获取请求中使用 promise,并将成功返回的数据附加到 DOMContentLoaded 回调中可用的内容,您可以在其中重复检查数据是否已分配。

let myData;

let doAsyncOperation = (param) => {
  if ( window.Promise ) {
    let promise = new Promise( (resolve, reject) => {

      // Do your async request here...
      window.setTimeout(() => {
        // resolve the promise with successfully returned data from get req.
        resolve(`Hello ${param}`)
      }, 1000);

      // reject the promise if your request returns an error
      if (typeof param !== "string") reject("param must be a string");

    })
    return promise;
  } else {
  	console.log("Sorry no promise for you, use a fallback ")
  }
} 

doAsyncOperation("Foo !").then(
  // on success do something with your data.
  (data) =>  myData = data,
  (err) => console.log(err)
);

// Inside DOMContentLoaded callback check if myData is available.
document.addEventListener("DOMContentLoaded", (event) => {
  let loop = () => {
  	(myData) ? console.log(myData) : window.setTimeout(loop, 100);
  };
  loop();
});

【讨论】:

  • 花了我一段时间才能够理解箭头函数的语法。为简单起见,我不会在与箭头函数无关的答案中使用这些。当然,这种方法本身是有效的。谢谢。
  • 抱歉箭头函数过载,只是想习惯最新的功能并一直使用相同的功能,除此之外我只是喜欢语法。 ;)
  • 我只是看着(data) => myData = data 两分钟,真的不知道那是什么。哦,好吧。
  • 如果只有一个参数,你甚至不需要括号,这个参数实际上可以更简化为data => myData = data。一开始我知道这真的很奇怪,但我已经掌握了窍门。
【解决方案2】:

当我需要等待几个异步事件时,我个人的做法通常是这样的:

const EVENT0 = 0x1;
const EVENT1 = 0x2;
const ALL_READY = 0x3;

var ready = 0;
init();

function init() {
  asyncRequest0();
  asyncRequest1();

  var waitFunc;

  (waitFunc = function() {
    if(ready == ALL_READY) {
      goOn();
    }
    else {
      setTimeout(waitFunc, 10);
    }
  })();
}

function goOn() {
  console.log('Here we go!');
}

function asyncRequest0() {
  // some callback will do:
  ready |= EVENT0;
}

function asyncRequest1() {
  // some callback will do:
  ready |= EVENT1;
}

因此,基本上,每个异步事件都会在其工作完成后在“就绪”变量中设置一个位。 waitFunc() 函数会耐心等待所有事件完成(无论以何种顺序)并调用 goOn() 函数。

现在,应用于您的代码:

const DOM_READY = 0x1;
const XHR_READY = 0x2;
const ALL_READY = 0x3;

var ready = 0;
var listData;

init();

function init() {
  document.addEventListener("DOMContentLoaded", function() {
    ready |= DOM_READY;
  });

  var xhr = new XMLHttpRequest();

  xhr.open('GET', 'records.json', false);
  xhr.addEventListener("load", function() {
    if (xhr.readyState === 4) {
      if (xhr.status === 200) {
        listData = JSON.parse(xhr.responseText);
      } else {
        listData = false;
        console.error('Error: ' + xhr.status + ' ' + xhr.statusText);
      }
      ready |= XHR_READY;
    }
  });
  xhr.addEventListener("error", function() {
    listData = false;
    ready |= XHR_READY;
    console.error('Error: ' + xhr.status + ' ' + xhr.statusText);
  });
  xhr.send(null);

  var waitFunc;

  (waitFunc = function() {
    if (ready == ALL_READY) {
      goOn();
    } else {
      setTimeout(waitFunc, 10);
    }
  })();
}

function goOn() {
  console.log('Here we go!');
  // do something with listData
}

您实际上并不需要仅针对 2 个事件的位掩码,但这是一次测试多个事件的便捷方式。

【讨论】:

  • 这种方法有效。我不理解的是,为什么当它不可用时将listData 设置为false。为什么不让它未定义?
  • @kleinfreund 你当然也可以不定义它。 (我通常将“错误”结果设置为“假”,但没有特别的理由这样做。)
猜你喜欢
  • 1970-01-01
  • 2017-09-25
  • 2015-07-19
  • 1970-01-01
  • 2018-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多