jQuery / .ajax() / Peticiones síncronas y alternativa asíncrona

async: false en jQuery.ajax() hace una petición síncrona: el flujo actual espera a que termine. Puede bloquear la ejecución principal y la interfaz. Para código nuevo, prefiera el modo asíncrono predeterminado y conecte el trabajo posterior. Una segunda petición que necesita la primera respuesta puede mantener el orden sin bloquear.

Qué significa async:false

La API de jQuery.ajax() usa true por defecto. false no acelera: impide continuar después de la llamada antes de completar. La guía MDN sobre peticiones síncronas y asíncronas explica la falta de respuesta resultante.

En código síncrono antiguo use opciones success, error y complete. Desde 1.8 está obsoleta la combinación async:false con procesamiento Deferred de jqXHR. No significa que todo Ajax o .done() asíncrono esté obsoleto. Los .then(), .done() y .fail() siguientes pertenecen a la alternativa asíncrona.

Si necesita orden, inicie la siguiente petición tras la respuesta y devuélvala para conectarla. Distinga peticiones paralelas independientes de aquellas que necesitan un valor anterior.

Servidor local con demora y cliente completo

Prepare Python 3, Internet y una carpeta nueva. Guarde ajax-server.py en UTF-8. Archivos y tres API comparten origen. /first devuelve id 7, /second usa parent y /slow espera más para el tiempo límite. Las demoras son condiciones deliberadas del ejercicio.

from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
import json
import sys
import time

class Handler(SimpleHTTPRequestHandler):
    def do_GET(self):
        url = urlparse(self.path)
        if url.path not in ("/first", "/second", "/slow"):
            return super().do_GET()
        time.sleep(2 if url.path == "/slow" else 0.4)
        if url.path == "/second":
            parent = parse_qs(url.query).get("parent", [""])[0]
            data = {"detail": "Details for " + parent}
        else:
            data = {"id": 7, "name": "Alpha"}
        body = json.dumps(data).encode("utf-8")
        self.send_response(200)
        self.send_header("Content-Type", "application/json; charset=utf-8")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        try:
            self.wfile.write(body)
        except (BrokenPipeError, ConnectionResetError):
            pass

if __name__ == "__main__":
    port = int(sys.argv[1]) if len(sys.argv) > 1 else 8000
    ThreadingHTTPServer(("127.0.0.1", port), Handler).serve_forever()

Guarde ajax-demo.html allí. Use el archivo oficial completo 4.0.0, no slim sin Ajax. Los inicios quedan deshabilitados mientras se trabaja y active guarda el jqXHR real actual. Devolver la segunda petición desde .then() hace que la cadena posterior también espere por ella.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Keep request order without blocking</title>
  <script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
  <style>
    body { font: 16px/1.6 sans-serif; max-width: 760px; margin: 24px auto; padding: 16px; }
    button { font: inherit; margin: 4px; max-width: 100%; }
    pre { white-space: pre-wrap; overflow-wrap: anywhere; }
  </style>
</head>
<body>
  <h1>Sequence asynchronous requests</h1>
  <button type="button" class="start" id="run">Run two requests</button>
  <button type="button" class="start" id="error">First request fails</button>
  <button type="button" class="start" id="timeout">First request times out</button>
  <button type="button" id="cancel" disabled>Cancel active request</button>
  <button type="button" id="reset">Reset</button>
  <p id="heartbeat">heartbeat=0</p>
  <pre id="result" aria-live="polite">Ready</pre>
  <script>
    $(function () {
      let active = null, busy = false, lines = [], ticks = 0;
      setInterval(function () { ticks++; $("#heartbeat").text(`heartbeat=${ticks}`); }, 100);
      function log(line) { lines.push(line); $("#result").text(lines.join("\n")); }
      function run(path, timeout) {
        if (busy) return;
        busy = true; lines = [];
        $(".start, #reset").prop("disabled", true); $("#cancel").prop("disabled", false);
        log("before");
        active = $.ajax({ url: path, dataType: "json", timeout: timeout });
        active.then(function (first) {
          log("first=" + first.name);
          active = $.ajax({ url: "/second", data: { parent: first.id }, dataType: "json", timeout: 1000 });
          return active;
        }).done(function (second) {
          log("second=" + second.detail);
        }).fail(function (xhr, textStatus) {
          log("failed=" + textStatus + "; HTTP=" + xhr.status);
        }).always(function () {
          log("complete"); active = null; busy = false;
          $(".start, #reset").prop("disabled", false); $("#cancel").prop("disabled", true);
        });
        log("returned to caller");
      }
      $("#run").on("click", function () { run("/first", 1000); });
      $("#error").on("click", function () { run("/missing.json", 1000); });
      $("#timeout").on("click", function () { run("/slow", 300); });
      $("#cancel").on("click", function () { if (active) active.abort(); });
      $("#reset").on("click", function () { lines = []; $("#result").text("Ready"); });
    });
  </script>
</body>
</html>

Salida inicial; un temporizador separado aumenta heartbeat cada 100ms.

Ready

Ejecute esto en la carpeta. Si falta python, revise instalación y ruta. Si 8000 está ocupado, use python ajax-server.py 8001 y cambie la dirección.

python ajax-server.py

Con el servidor activo, abra http://127.0.0.1:8000/ajax-demo.html, no file://. Internet carga la red de distribución de contenido (CDN) de jQuery. Detenga con Ctrl+C al acabar. Se ejecutó con Windows Edge, 3.7.1, 4.0.0 y servidor Python; solo se sustituyó el puerto de verificación por uno libre.

Verificar orden, errores y cancelación

  1. Run two requests muestra primero before y returned to caller. heartbeat continúa durante la espera.
  2. Después aparecen first=Alpha, second=Details for 7 y complete. Network muestra /second?parent=7 tras la primera respuesta, no un inicio simultáneo.
  3. First request fails produce failed=error; HTTP=404 y complete, sin iniciar la segunda.
  4. First request times out limita la demora de dos segundos de /slow a 300ms: failed=timeout; HTTP=0. Cero no es un código HTTP enviado por el servidor.
  5. Cancel active request cancela la petición actual: failed=abort; HTTP=0. Tras la primera respuesta, active apunta a la segunda.
  6. Al acabar se habilitan los controles. Reset devuelve solo la salida a Ready; heartbeat continúa. Compruebe un intento normal tras fallar.

.always() restaura controles y busy tanto en éxito como en error. Limpiar solo en éxito podría dejar botones bloqueados tras fallar. Distinga la cadena del jqXHR cancelable y actualice active por cada $.ajax(). Cancelar no deshace trabajo ya ejecutado en el servidor.

Observar el orden síncrono antiguo

Con servidor activo y otras peticiones terminadas, ejecute esto una vez en la consola. Explica async:false usando solo /first local; no lo cambie por una dirección externa lenta.

console.log("before synchronous request");
setTimeout(function () { console.log("timer after synchronous request"); }, 0);
$.ajax({
  url: "/first",
  dataType: "json",
  async: false,
  success: function (data) { console.log("success=" + data.name); },
  error: function (xhr, textStatus) { console.log("failed=" + textStatus); }
});
console.log("after synchronous request");
before synchronous request
success=Alpha
after synchronous request
timer after synchronous request

success precede a la línea posterior; el temporizador de cero demora ejecuta después de la llamada síncrona y el código siguiente. Cero milisegundos no interrumpe el flujo actual. Ocultar la advertencia de obsolescencia no soluciona el bloqueo.

Escribir Loading antes no garantiza tiempo para dibujar ni procesar temporizadores y entradas. Compare heartbeat asíncrono para distinguir esperar una respuesta de bloquear el flujo principal.

Cambiar código existente

Cambiar solo async a true puede leer una variable externa antes de estar lista. Mueva su uso a la finalización o devuelva petición/Promise para esperar. Crear una petición dentro de .then() sin devolverla no hace que la cadena espere.

Tiempo límite, respuesta mal formada, red y cancelación son resultados distintos. Valide datos en programas reales. Añadir opciones de tiempo o cancelación no elimina el bloqueo síncrono. Para envíos o guardados, adapte reintentos a las consecuencias de repetir una operación.