Hi,

While I have some basic js coding skills and a few APIs uses in the past, I get stuck with one because it basically contains 3 calls. All async.
Step 1 is pushing a file to the service and get back an ID in return.
Step 2 is requiring a specific operation on the previously pushed file thanks to the ID
Step 3 is downloading the result

The fact is that I need to ensure all of these steps are individually ok before calling the next API endpoint. And exit if something went wrong.
I am having hard times to sequence those three steps.

Eventually, I wish I can manage the 3 steps in one function call as :

function main(){
    //Pseudo code
    calling async step1
    exit if step1 failed
    calling async step2
    exit if step2 failed
    calling async ste3
    exit if step3 failed
    return OK
}

What I am looking right now is advices on the strategy to tackle this. Right now I am loosing myself in promises/await loops and get crazy.
Thanks in advance for your valuable insights.

Dani AI

Generated

Good problem statement from and a solid start from — linear async/await is the right mental model. Add a few production-safe habits and the sequence becomes predictable: (1) make each step a small function that either resolves a validated result or throws, (2) wrap the three calls in a single async function with a top-level try/catch, (3) handle long-running server work by polling, and (4) add timeouts/cancellation and optional retries for transient errors.

Key rules to follow: validate HTTP status before parsing JSON, fail-fast on missing fields (for example no id), treat a 202/queued response as “work in progress” and poll until done or timeout, and prefer throwing errors from helpers so the happy path stays linear. For cancellation use AbortController where available; for transient 5xx/network failures use a small retry with exponential backoff.

A compact pattern that implements those ideas:

async function main(file) {
  try {
    const { id } = await uploadFile(file); // should throw on HTTP/network errors
    if (!id) throw new Error('upload failed: missing id');

    const op = await requestOperation(id); // may be { status, opId, resultUrl }
    const finished = op.status === 'done'
      ? op
      : await pollOperation(op.opId, { interval: 1000, timeout: 60000 });

    if (finished.status !== 'done') throw new Error('operation did not complete');
    const result = await downloadResult(finished.resultUrl);
    if (!result) throw new Error('download failed');

    return result;
  } catch (err) {
    console.error('pipeline error:', err);
    throw err; // surface to caller or UI
  }
}

async function pollOperation(opId, { interval = 1000, timeout = 60000 } = {}) {
  const start = Date.now();
  while (Date.now() - start < timeout) {
    const status = await getOperationStatus(opId); // should throw on HTTP errors
    if (status.status === 'done') return status;
    if (status.status === 'error') throw new Error(status.message || 'operation error');
    await new Promise(r => setTimeout(r, interval));
    interval = Math.min(interval * 1.5, 5000); // gentle backoff
  }
  throw new Error('operation timed out');
}

Practical extras: centralize retry logic for 5xx, keep functions idempotent when possible, expose clear error objects for the UI, and write small unit tests that mock each server state (ok, queued -> done, error, timeout). This extends ’s linear awaits by adding validation, polling, timeouts, and fail-fast error handling so the three-step flow is robust and easy to reason about.

The new Async/Await makes super easy to handle this; I am just gonna assume some names and give an example:

/*************/
/* Fake Functions */

const timeOutPromise = (timeout, returnData) => new Promise(resolve => {
    setTimeout(() => {
        resolve(returnData);
    }, timeout);
})

const fakeFileUpload = () => fetch(
    'https://jsonplaceholder.typicode.com/posts',
    { method: 'POST' }
).then(response => response.json())

const fakeAjax = (id) => fetch(`https://jsonplaceholder.typicode.com/todos/${id}`)
    .then(response => response.json())

/*************/
/* Async project functions */
/*
 * @return {{ id: string }}
 */
const uploadFile = async (myFile) => fakeFileUpload(myFile);

/*
 * @return {{ status: string, url: string }}
 */
const processData = async (data) => fakeAjax(data.id)

/*
 * @return {void}
 */
const downloadFile = async (info) => timeOutPromise(500, {status: 'OK', url: 'https://jsonplaceholder.typicode.com/todos/1'})

/*************/
/* Main Function e.g. Event Handler */
const doAction = async () => {
  const fileInfo = await uploadFile();
  console.log(fileInfo)
  if(!fileInfo) {
      return;
  }

  const processedInfo = await processData(fileInfo);
  console.log(processedInfo)
  if(!processedInfo) {
      return;
  }

  const downloadInfo = await downloadFile(processedInfo);
  console.log(downloadInfo)
  if(!downloadInfo) {
      return;
  }
  return downloadInfo;
}
/* ********** */
/* trigger main Function */
doAction();

You can see above code block in action here: https://codepen.io/pankajpatel/pen/gObmwmp?editors=0012

I wrote about the recepies of async await here https://time2hack.com/5-code-recipes-asynchronous-code-execution-with-promise-async-await/

let me know if you need some help

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.