jQuery / .ajax() / Requisições síncronas e alternativa assíncrona

async: false em jQuery.ajax() cria uma requisição síncrona: o fluxo atual espera até concluir. Isso pode bloquear a execução principal e a interface. Em código novo, prefira o modo assíncrono padrão e conecte o trabalho seguinte. Uma segunda requisição que precisa da primeira resposta pode manter a ordem sem bloquear.

Significado de async:false

A API de jQuery.ajax() usa true por padrão. false não acelera: impede continuar após a chamada antes de concluir. O guia MDN de requisições síncronas e assíncronas explica a interface sem resposta.

Em código síncrono antigo, use opções success, error e complete. Desde 1.8, a combinação async:false com processamento Deferred do jqXHR está obsoleta. Isso não torna todo Ajax ou .done() assíncrono obsoleto. .then(), .done() e .fail() abaixo pertencem à alternativa assíncrona.

Se a necessidade é ordem, inicie a próxima requisição após a resposta e retorne-a para conectar. Diferencie trabalho paralelo independente de requisições que precisam de um valor anterior.

Servidor local com atraso e cliente completo

Prepare Python 3, Internet e uma pasta nova. Salve ajax-server.py em UTF-8. Arquivos e três APIs têm a mesma origem. /first fornece id 7, /second usa parent e /slow espera mais para o timeout. Os atrasos são condições intencionais do exercício.

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

Salve ajax-demo.html ali. Use o arquivo oficial completo 4.0.0, não slim sem Ajax. Inícios são desabilitados durante o trabalho e active guarda o jqXHR real atual. Retornar a segunda requisição de .then() faz a cadeia seguinte aguardar também sua conclusão.

<!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>

Saída inicial; um temporizador separado aumenta heartbeat a cada 100ms.

Ready

Execute isto na pasta. Se faltar python, confira instalação e caminho. Se 8000 estiver ocupada, use python ajax-server.py 8001 e ajuste o endereço.

python ajax-server.py

Com o servidor ativo, abra http://127.0.0.1:8000/ajax-demo.html, não file://. A Internet carrega a rede de distribuição de conteúdo (CDN) do jQuery. Encerre com Ctrl+C. Executou-se com Windows Edge, 3.7.1, 4.0.0 e servidor Python; só a porta de verificação mudou para uma disponível.

Verificar ordem, falha e cancelamento

  1. Run two requests mostra primeiro before e returned to caller. heartbeat continua durante a espera.
  2. Depois vêm first=Alpha, second=Details for 7 e complete. Network mostra /second?parent=7 após a primeira resposta, sem início simultâneo.
  3. First request fails produz failed=error; HTTP=404 e complete, sem iniciar a segunda.
  4. First request times out limita o atraso de dois segundos de /slow a 300ms: failed=timeout; HTTP=0. Zero não é um código HTTP enviado pelo servidor.
  5. Cancel active request cancela a requisição atual: failed=abort; HTTP=0. Após a primeira resposta, active aponta para a segunda.
  6. Ao concluir, os controles são habilitados. Reset muda apenas a saída para Ready; heartbeat continua. Confira uma tentativa normal após falha.

.always() restaura controles e busy no sucesso ou erro. Limpar só no sucesso poderia deixar botões bloqueados após falha. Diferencie a cadeia do jqXHR cancelável e atualize active a cada $.ajax(). Cancelar não desfaz trabalho já executado pelo servidor.

Observar a ordem síncrona antiga

Com servidor ativo e outras requisições concluídas, execute isto uma vez no Console. Explica async:false apenas com /first local; não substitua por endereço externo lento.

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 linha após a chamada; o temporizador de atraso zero executa depois da chamada síncrona e do código seguinte. Zero milissegundos não interrompe a execução atual. Ocultar aviso de obsolescência não resolve o bloqueio.

Escrever Loading antes não garante tempo para desenhar nem processar temporizadores e entradas. Compare heartbeat assíncrono para diferenciar esperar uma resposta de bloquear o fluxo principal.

Alterar código existente

Mudar só async para true pode ler uma variável externa antes de estar pronta. Mova seu uso para a conclusão ou retorne requisição/Promise para aguardar. Criar uma requisição em .then() sem retornar não faz a cadeia esperar.

Timeout, resposta malformada, falha de rede e cancelamento são distintos. Valide dados em programas reais. Opções de tempo ou cancelamento não removem o bloqueio síncrono. Para envio ou salvamento, adapte novas tentativas às consequências de repetir a operação.