asynchronous programming
callbacks
animation
ajax
web development

How to properly fire a callback after an animation and ajax calls have been successfully completed?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

When UI animation and AJAX requests run together, firing a callback too early can create inconsistent state and visual glitches. The reliable pattern is to model both tasks as asynchronous operations and wait until both complete. Modern JavaScript makes this straightforward with promises.

Model Animation and Request as Promises

Convert each asynchronous action into a promise. Then combine them with Promise.all and run the final callback once both are resolved.

html
<div id="panel" style="display:none">Loaded content</div>
javascript
1function animatePanel() {
2  return new Promise((resolve) => {
3    const panel = document.getElementById("panel");
4    panel.style.display = "block";
5    panel.animate(
6      [
7        { opacity: 0, transform: "translateY(10px)" },
8        { opacity: 1, transform: "translateY(0px)" },
9      ],
10      { duration: 300, easing: "ease-out", fill: "forwards" }
11    ).onfinish = resolve;
12  });
13}
14
15function loadData() {
16  return fetch("/api/items")
17    .then((r) => {
18      if (!r.ok) throw new Error("request failed");
19      return r.json();
20    });
21}
22
23function runAfterDone() {
24  Promise.all([animatePanel(), loadData()])
25    .then(([_, data]) => {
26      console.log("Animation and AJAX completed", data.length);
27    })
28    .catch((err) => {
29      console.error("Flow failed", err);
30    });
31}

This keeps orchestration explicit and avoids callback nesting.

Async Await Version

The same logic can be written with async and await for readability.

javascript
1async function runFlow() {
2  try {
3    const [_, data] = await Promise.all([animatePanel(), loadData()]);
4    console.log("Completed", data.length);
5  } catch (err) {
6    console.error(err);
7  }
8}

Use this style when workflows include additional steps such as form validation, optimistic UI updates, or retry logic.

jQuery Interop Pattern

In legacy codebases, you may still use jQuery animation and $.ajax. You can wrap jQuery completion callbacks in promises and keep the same coordination strategy.

javascript
1function animatePanelJQ() {
2  return new Promise((resolve) => {
3    $("#panel").fadeIn(300, resolve);
4  });
5}
6
7function loadDataJQ() {
8  return new Promise((resolve, reject) => {
9    $.ajax({
10      url: "/api/items",
11      method: "GET",
12      success: resolve,
13      error: reject,
14    });
15  });
16}
17
18Promise.all([animatePanelJQ(), loadDataJQ()])
19  .then(([_, data]) => {
20    console.log("Legacy flow done", data.length);
21  })
22  .catch(console.error);

This allows gradual modernization without rewriting the whole frontend stack at once.

Error and Cancellation Handling

When one task fails, decide whether to cancel or continue the other task. For example, if the request fails but animation succeeds, you may show an error panel in the same animated region.

Use timeout guards for slow requests and protect callbacks from running after component unmount in framework based apps. In React or Vue, pair promise logic with lifecycle cleanup to avoid updating destroyed views.

For complex pages, consider a small state machine with states such as idle, loading, animating, ready, and error. The final callback should only fire when state transitions confirm both operations completed successfully. This approach prevents accidental double firing when users trigger the flow repeatedly.

Idempotent callback handlers are another safety layer. If a race condition happens, repeated callback execution should not duplicate DOM nodes or send duplicate analytics events.

Automated tests should simulate both fast and slow network responses so callback order is verified under timing variation. In end to end tests, assert final UI state rather than intermediate events to keep tests robust while still validating that completion logic waits for both operations.

Common Pitfalls

A common pitfall is putting the callback in animation completion only, while request still runs. This can trigger UI logic with missing data.

Another issue is mixing callbacks and promises inconsistently, which creates duplicate completion paths and race conditions.

Developers also ignore error branches. If either animation or AJAX fails and errors are not handled, the final callback may never run and UI appears stuck.

Finally, relying on fixed delays instead of actual completion events is brittle. Network and rendering times vary by device and environment.

Summary

  • Treat animation and AJAX as separate async tasks.
  • Use Promise.all to fire final callback only after both complete.
  • Prefer async await for readable orchestration in modern code.
  • Wrap legacy jQuery callbacks in promises for consistent flow control.
  • Handle failure and cancellation paths explicitly to keep UI reliable.

Course illustration
Course illustration

All Rights Reserved.