如何在JavaScript中强制程序等待HTTP请求完成?

33

在JavaScript中,有没有一种方法可以向HTTP服务器发送HTTP请求并等待直到服务器响应?我希望我的程序等待直到服务器回复,并且不执行在此请求之后的任何其他命令。如果HTTP服务器宕机,我希望在超时后重复HTTP请求,直到服务器响应,然后程序的执行可以正常继续。

有什么想法吗?

先感谢您, Thanasis


只是一点小提示,在jQuery中你要找的是ajaxStop - Joshua Pinter
8个回答

49

编辑:同步请求现已弃用,您应始终以异步方式处理HTTP请求。

XmlHttpRequestopen()有第三个参数,旨在指示您希望该请求是异步的(因此通过onreadystatechange处理响应)。

因此,如果您希望它是同步的(即等待答案),只需为此第三个参数指定false。 在这种情况下,您可能还想为请求设置有限的timeout属性,因为它会阻止页面直到接收到响应。

以下是一个包含同步和异步的全功能示例函数:

function httpRequest(address, reqType, asyncProc) {
  var req = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
  if (asyncProc) { 
    req.onreadystatechange = function() { 
      if (this.readyState == 4) {
        asyncProc(this);
      } 
    };
  }
  req.open(reqType, address, !(!asyncProc));
  req.send();
  return req;
}

你可以这样称呼它:
var req = httpRequest("http://example.com/aPageToTestForExistence.html", "HEAD");  // In this example you don't want to GET the full page contents
alert(req.status == 200 ? "found!" : "failed");  // We didn't provided an async proc so this will be executed after request completion only

1
这让我想到了async库,它使得这个过程非常简单。https://github.com/caolan/async/ 不要被包装的复杂性所迷惑。你只需要包含一个小的JavaScript文件 - async.js! - tudor -Reinstate Monica-
3
今天我第一次尝试同步请求,因为我有与初次发帖者相似的需求。然而,在Chrome浏览器上,我却收到了一个错误提示:"InvalidAccessError: Failed to execute 'open' on 'XMLHttpRequest': Synchronous requests must not set a timeout."(无法执行 XMLHttpRequest 上的 open 方法:同步请求不能设置超时)。考虑到同步请求的本质,这样的限制似乎很奇怪,但这就是结果。挺诡异的! - Randy
我使用你的函数时出现了错误:“同步XMLHttpRequest不支持超时和响应类型”。 - Gupta
确实,@Gupta。这曾经有效,但现在不再有效了。顺便说一下,同步HTTP请求现已被弃用,因此您应该始终避免使用它们并转向异步。 - Jérôme Beau

8
您可以执行同步请求。jQuery示例:
$(function() {
    $.ajax({
       async: false,
       // other parameters
    });
});

你应该看一下jQuery的AJAX API。我强烈建议使用像jQuery这样的框架来处理这些东西。手动进行跨浏览器的ajax真的很麻烦!


这是最好的方式!然而,许多人认为JS框架是邪恶的,并倾向于自己编写所有代码。 - Alex
我已经尝试过多种方式,例如使用XmlHttpRequest,但我还没有找到一种方法来停止执行,直到我得到一个响应。 - Thanasis Petsas
7
亚历克斯,就是我!我是他们中的一员! ;) - Delan Azabani
如果您不需要JS框架,只需使用async.js中的async.waterfall()与普通的XmlHttpRequest回调即可。https://github.com/caolan/async/ - tudor -Reinstate Monica-

8
您可以使用XMLHttpRequest对象发送请求。一旦请求被发送,您可以检查readyState属性以确定当前状态。readyState将有以下不同的状态。
  • 未初始化 - 尚未开始加载
  • 正在加载 - 正在加载
  • 交互 - 已加载足够内容,并且用户可以进行交互
  • 完成 - 完全加载
例如:
xmlhttp.open("GET","somepage.xml",true);
xmlhttp.onreadystatechange = checkData;
xmlhttp.send(null);

function checkData()
{
    alert(xmlhttp.readyState);
}

希望这能有所帮助


2
我希望程序的执行是串行的,而且不想使用回调函数。如果可能的话,我希望在“xmlhttp.send(null);”调用时停止程序的执行,然后在请求完成后继续执行。 - Thanasis Petsas
你可以操作readyState属性。也许你可以把它放在while循环中,这样它就不会进入下一行直到执行完成。此外,如果你能够预测执行时间,你可以使用setTimeOut()函数。关于该函数的更多信息可以在这里阅读。http://www.w3schools.com/jsref/met_win_settimeout.asp - Ashif Nataliya
这对我有效,谢谢。以下是可能的就绪状态:0:未发送,1:已打开,2:已接收标头,3:正在加载,4:已完成。 - chris
这似乎比等待或阻塞执行要好。 - FindOutIslamNow

2

对于现代浏览器,我将使用fetch而不是XMLHttpRequest。

async function job() {
  const response = await fetch("https://api.ipify.org?format=json", {}) // type: Promise<Response>
  if (!response.ok) {
    throw Error(response.statusText)
  }
  return response.text()
}


async function onCommit() {
  const result = await job()
  // The following will run after the `job` is finished.
  console.log(result)
}

一个例子

<button onclick="onCommit()">Commit</button>
<script>
  function onCommit() {
    new Promise((resolve, reject) => {
      resolve(job1())
    }).then(job1Result => {
      return job2(job1Result)
    }).then(job2Result => {
      return job3(job2Result)
    }).catch(err => { // If job1, job2, job3, any of them throw the error, then will catch it.
      alert(err)
    })
  }

  async function testFunc(url, options) {
    // options: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch
    const response = await fetch(url, options) // type: Promise<Response>
    if (!response.ok) {
      const errMsg = await response.text()
      throw Error(`${response.statusText} (${response.status}) | ${errMsg} `)
    }
    return response
  }

  async function job1() {
    console.log("job1")
    const response =  await testFunc("https://api.ipify.org?format=json", {})
    return await response.json()
  }

  async function job2(job1Data) {
    console.log("job2")
    console.log(job1Data)
    const textHeaders = new Headers()
    textHeaders.append('Content-Type', 'text/plain; charset-utf-8')
    const options = {"headers": textHeaders}
    const response = await testFunc("https://api.ipify.org/?format=text", options)
    // throw Error(`test error`) // You can cancel the comment to trigger the error.
    return await response.text()
  }

  function job3(job2Data) {
    console.log("job3")
    console.log(job2Data)
  }
</script>


0

我在使用Three.js和Google Closure构建的游戏中遇到了类似的情况。我需要加载两个资源,但是Three和Closure不允许我将它们同步加载。

最初我天真地写了以下代码:

main() {

  ...
  var loaded=0;
  ...

  // Load Three geometry
  var loader = new THREE.JSONLoader();
  loader.load("x/data.three.json", function(geometry) {
    ...
    loaded++;
    });

   // Load my engine data
  goog.net.XhrIo.send("x/data.engine.json", function(e) {
    var obj = e.target.getResponseJson();
    ...
    loaded++;
    });

  // Wait for callbacks to complete
  while(loaded<2) {}

  // Initiate an animation loop
  ...
};

等待回调完成的循环从循环的角度来看永远不会结束,因为loaded从未增加。问题在于,在main返回之前(至少在Chrome上是这样),回调不会被触发。

一种解决方案可能是让两个回调都检查是否是最后一个完成的,然后继续启动动画循环。

另一种解决方案 - 也许更直接地回答了你所问的问题(如何在启动另一个之前等待每次加载)- 是将回调嵌套如下:

// Load Three geometry
var loader = new THREE.JSONLoader();
loader.load("x/data.three.json", function(geometry) {
  ...

   // Load my engine data
   goog.net.XhrIo.send("x/data.engine.json", function(e) {
     var obj = e.target.getResponseJson();
     ...

     // Initiate an animation loop
     ...

    });
  });
};

1
嗨,Argenti,欢迎来到stackoverflow!请尽可能清晰地回答问题。使用花括号或使用 ` 字符包装您的代码。在发布之前,请尝试查看是否可以运行某些内容。这些问题可能会在将来被提及,因此请努力付出努力! - Maarten Bodewes
我找到了这个:注意:从Gecko 30.0(Firefox 30.0 / Thunderbird 30.0 / SeaMonkey 2.27)开始,由于对用户体验的负面影响,主线程上的同步请求已被弃用。Mozilla Dev.Network - B.F.

0

为此,您可以在页面开始加载时立即启动JavaScript中的加载程序,然后在请求完成或DOM准备就绪时关闭它。 我的意思是,当页面加载开始时,启动一个加载程序。然后,页面可以使用ajax进行多个同步请求,直到您获得响应为止,请勿关闭加载程序。 在最终调用中收到所需的响应后,您可以关闭加载程序。


1
你离线了... 他正在询问如何阻塞多个请求。 - Alex
@Thanasis Petsas:请检查编辑过的帖子。希望这能帮助你。 - Nik
你能否提供一个简单的例子吗? - Thanasis Petsas

0
这是一个老问题,但我想提供一个不同的观点。
这是一个异步函数,它创建一个在请求完成时解析为 Http 对象的 Promise。这使您可以在使用 XMLHttpRequest 时使用更现代的 async/await 语法。
async sendRequest() {
    const Http = new XMLHttpRequest();
    const url='http://localhost:8000/';
    Http.open("GET", url);
    Http.send();

    if (Http.readyState === XMLHttpRequest.DONE) {
        return Http;
    }

    let res;
    const p = new Promise((r) => res = r);
    Http.onreadystatechange = () => {
        if (Http.readyState === XMLHttpRequest.DONE) {
            res(Http);
        }
    }
    return p;
}

使用方法

const response = await sendRequest();
const status = response.status;
if (status === 0 || (status >= 200 && status < 400)) {
    // The request has been completed successfully
    console.log(response.responseText);
} else {
    // Oh no! There has been an error with the request!
    console.log(`Server Error: ${response.status}`)
}

-1

对于那些使用axios的人,你可以将其包装在一个async iife中,然后await它:

(async () => {
  let res = await axios.get('https://example.com');

  // do stuff with the response
})();

请注意,这里我没有进行任何错误检查。

我知道这个问题很久以前就被发布了,但是我想指出这一点,因为我发现它非常有用。 - applemonkey496

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接