在 JavaScript 中,我/应该如何将 async/await 与 XMLHttpRequest 一起使用?

IT技术 javascript async-await xmlhttprequest
2021-02-09 02:25:53

完全披露:我认为自己具有中级 JavaScript 知识。所以这略高于我此时的经验水平。

我有一个 Google Chrome 扩展程序,它会file:///在页面加载后立即向本地发出 AJAX 请求从请求中得到响应后,我稍后在代码中的几个函数中使用返回的代码。大多数情况下,我会在需要它的代码运行之前得到响应。但有时我不这样做,一切都会破裂。

现在,我假设我可以将所有相关代码扔到xhr.onload下面。但这似乎真的效率低下?我有很多依赖响应的活动部件,把它们都放在那里似乎很糟糕。

我已经阅读了几篇与 async/await 相关的文章,但我无法理解这个概念。我也不是 100% 肯定我正在以正确的方式看待这个问题。我是否应该考虑使用 async/await?

这是我的 AJAX 请求的代码。

  var xhr = new XMLHttpRequest();
  xhr.open("GET", url, true);
  xhr.onload = function(e) {
    code = xhr.response;
  };
  xhr.onerror = function () {
    console.error("** An error occurred during the XMLHttpRequest");
  };
  xhr.send();

假设我有一堆函数需要稍后在我的代码中触发。现在它们看起来像:

function doTheThing(code) {
  // I hope the response is ready.
}

解决这个问题的最佳方法是什么?仅供参考,FetchAPI 不是一种选择。

这是我的代码结构的高级视图。

// AJAX request begins.

// ...

// A whole bunch of synchronous code that isn't dependant on 
// the results of my AJAX request. (eg. Creating and appending
// some new DOM nodes, calculating some variables) I don't want
// to wait for the AJAX response when I could be building this stuff instead.

// ...

// Some synchronous code that is dependant on both my AJAX 
// request and the previous synchronous code being complete.

// ...

// Some more synchronous code that needs the above line to 
// be complete.
5个回答

我通常像这样执行异步/等待:

async function doAjaxThings() {
    // await code here
    let result = await makeRequest("GET", url);
    // code below here will only execute when await makeRequest() finished loading
    console.log(result);
}
document.addEventListener("DOMContentLoaded", function () {
    doAjaxThings();
    // create and manipulate your DOM here. doAjaxThings() will run asynchronously and not block your DOM rendering
    document.createElement("...");
    document.getElementById("...").addEventListener(...);
});

此处Promise的 xhr 功能

function makeRequest(method, url) {
    return new Promise(function (resolve, reject) {
        let xhr = new XMLHttpRequest();
        xhr.open(method, url);
        xhr.onload = function () {
            if (this.status >= 200 && this.status < 300) {
                resolve(xhr.response);
            } else {
                reject({
                    status: this.status,
                    statusText: xhr.statusText
                });
            }
        };
        xhr.onerror = function () {
            reject({
                status: this.status,
                statusText: xhr.statusText
            });
        };
        xhr.send();
    });
}
我刚刚更新了我的例子。但是,如果您希望您的 2 个或更多独立任务并行(异步)运行,并且仅在所有任务完成后运行一些其他代码,请查看 Promise.all()。 developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
2021-03-23 02:25:53
@ThắngTrầnXuân 如果我每天都能为你的答案点赞,我会的!!!您刚刚结束了我多年来经历的最令人沮丧的开发会议!谢谢!谢谢!谢谢!
2021-03-30 02:25:53
感谢您的解释。我已经编辑了我上面的问题,以简要介绍我的代码当前的结构。您是否仍会基于此向我推荐您的答案?
2021-04-02 02:25:53
就像它所说的那样,您需要将任何内容await放在异步函数上。只需asyncfunction文本之前添加async function doTheThing(code) { let result = await makeRequest("GET", url); console.log(result); }
2021-04-06 02:25:53
Uncaught SyntaxError: await is only valid in async function
2021-04-07 02:25:53

我为 XHR 创建了一个Promise。然后只需awaitasync函数内部使用即可调用它。

function getHTML(url) {
    return new Promise(function (resolve, reject) {
        var xhr = new XMLHttpRequest();
        xhr.open('get', url, true);
        xhr.responseType = 'document';
        xhr.onload = function () {
            var status = xhr.status;
            if (status == 200) {
                resolve(xhr.response.documentElement.innerHTML);
            } else {
                reject(status);
            }
        };
        xhr.send();
    });
}

async function schemaPageHandler(){
    try {
        var parser = new window.DOMParser();
        var remoteCode = await getHTML('https://schema.org/docs/full.html');
        var sourceDoc = parser.parseFromString(remoteCode, 'text/html');
        var thingList = sourceDoc.getElementById("C.Thing");
        document.getElementById("structured-data-types").appendChild(thingList);
    } catch(error) {
        console.log("Error fetching remote HTML: ", error);
    }              
}
为什么在这里使用 DOMParser?当您使用xhr.responseType = 'document';xhr.response已经是类型的HTMLDocument的。如果你改为解析,resolve(xhr.response)那么你会直接得到你现在得到的东西在sourceDoc我看来,现在你在 XHR 中解析 DOM,然后对结果进行字符串化(innerHTML),然后再次 DOM 解析它。或者我错过了什么?
2021-03-30 02:25:53
感谢您提供全面的示例。我不太确定 onload 方法中的 reject() 。可以肯定的是,如果您真的想处理所有错误,则应该有 onerror 处理程序。但是,对于以 2xx 状态(OK)结尾的情况以外的情况是否会触发 onload,我没有找到明确的答案。有测试说它不会发生,但另一方面在这个文档的第一个例子中,他们确实检查状态
2021-04-12 02:25:53

你有两个选择,

首先是使用fetch基于Promise的较新的api,你可以做

let response = await fetch(url);
response = await response.json();; // or text etc..
// do what you wanna do with response

如果你真的想使用 XMLHttpRequest 的其他选择是Promise它

let response = await new Promise(resolve => {
   var xhr = new XMLHttpRequest();
   xhr.open("GET", url, true);
   xhr.onload = function(e) {
     resolve(xhr.response);
   };
   xhr.onerror = function () {
     resolve(undefined);
     console.error("** An error occurred during the XMLHttpRequest");
   };
   xhr.send();
}) 
// do what you wanna do with response

可能的完整解决方案

(async () => {
   let response = await new Promise(resolve => {
      var xhr = new XMLHttpRequest();
      xhr.open("GET", url, true);
      xhr.onload = function(e) {
        resolve(xhr.response);
      };
      xhr.onerror = function () {
        resolve(undefined);
        console.error("** An error occurred during the XMLHttpRequest");
      };
      xhr.send();
   }) 
   doTheThing(response)
})()
我收到这个错误 Uncaught SyntaxError: await is only valid in async function
2021-03-16 02:25:53
@jkupczak 您需要在异步函数中包含这些位,例如:async function fn () {},如果您在顶层执行此操作,则可以将代码放入异步 IIFE(async function fn() {})()
2021-03-21 02:25:53
@jkupczak 当我写的时候// do what you wanna do with response,响应已经存在,你可以在那里调用你的函数,例如,替换// do what you wanna do with responsedoTheThing(response)
2021-03-28 02:25:53
当你说// do what you wanna do with response,我该怎么做?我稍后有一个功能,我只想在响应回来后触发。我不知道如何编码。
2021-04-02 02:25:53
谢谢你的回答。不幸的是,fetch不允许file:///这是我需要的。我会试试你的XMLHttpRequest选择,看看是否适合我。
2021-04-03 02:25:53

例如,您可以创建一个异步类来代替原始类来使用。它缺少一些方法,但可以作为示例。

(function() {
    "use strict";
    
    var xhr = Symbol();
    
    class XMLHttpRequestAsync {
        constructor() {
            this[xhr] = new XMLHttpRequest();
        }
        open(method, url, username, password) {
            this[xhr].open(method, url, true, username, password);
        }
        send(data) {
            var sxhr = this[xhr];
            return new Promise(function(resolve, reject) {
                var errorCallback;
                var loadCallback;
                
                function cleanup()  {
                    sxhr.removeEventListener("load", loadCallback);
                    sxhr.removeEventListener("error", errorCallback);
                }
                
                errorCallback = function(err) {
                    cleanup();
                    reject(err);
                };
                
                loadCallback = function() {
                    resolve(xhr.response);
                };
                
                
                sxhr.addEventListener("load", loadCallback);
                sxhr.addEventListener("error", errorCallback);
                
                
                sxhr.addEventListener("load", function load() {
                    sxhr.removeEventListener("load", load);
                    resolve(sxhr.response);
                });
                sxhr.send(data);
            });
        }
        set responseType(value)
        {
            this[xhr].responseType = value;
        }
        setRequestHeader(header, value) {
            this[xhr].setRequestHeader(header, value);
        }
    }
    
    addEventListener("load", async function main() {
        removeEventListener("load", main);


        var xhra = new XMLHttpRequestAsync();
        xhra.responseType = "json";
        xhra.open("GET", "appserver/main.php/" + window.location.hash.substring(1));
        console.log(await xhra.send(null));
        
    });
    
}());

我遇到了同样的问题并使用以下函数解决了它:

const makeRequest = (method, url, data = {}) => {
  const xhr = new XMLHttpRequest();
  return new Promise(resolve => {
    xhr.open(method, url, true);
    xhr.onload = () => resolve({
      status: xhr.status,
      response: xhr.responseText
    });
    xhr.onerror = () => resolve({
      status: xhr.status,
      response: xhr.responseText
    });
    if (method != 'GET') xhr.setRequestHeader('Content-Type', 'application/json');
    data != {} ? xhr.send(JSON.stringify(data)) : xhr.send();
  })
}

const test = async() => {
  console.log("Starting request ...")
  let request = await makeRequest("GET", "https://jsonplaceholder.typicode.com/todos/1");
  console.log("status:", request.status)
  console.log("response:", request.response)
}
test()