mercredi 29 septembre 2021

Managing sync and async functions in an algorithm call stack

TLDR: must I use async and await through a complicated call stack if most functions are not actually async? Are there alternative programming patterns?

Context

This question is probably more about design patterns and overall software architecture than a specific syntax issue. I am writing an algorithm in node.js that is fairly involved. The program flow involves an initial async call to fetch some data, and then moves on to a series of synchronous calculation steps based on the data. The calculation steps are iterative, and as they iterate, they generate results. But occasionally, if certain conditions are met by the calculations, more data will need to be fetched. Here is a simplified version in diagram form:

enter image description here

The calcStep loop runs thousands of times sychronously, pushing to the results. But occasionally it kicks back to getData, and the program must wait for more data to come in before continuing on again to the calcStep loop.

In code

A boiled-down version of the above might look like this in JS code:

let data;

async function init() {
  data = await getData();
  processData();
  calcStep1();
}

function calcStep1() {
  // do some calculations
  calcStep2();
}

function calcStep2() {
  // do more calculations
  calcStep3();
}

function calcStep3() {
  // do more calculations
  pushToResults();

  if (some_condition) {
    getData(); // <------ question is here
  }

  if (stop_condition) {
    finish();
  } else {
    calcStep1();
  }
}

Where pushToResults and finish are also simple synchronous functions. I write the calcStep functions here are separate because in the real code, they are actually class methods from classes defined based on separation of concerns.

The problem

The obvious problem arises that if some_condition is met, and I need to wait to get more data before continuing the calcStep loop, I need to use the await keyword before calling getData in calcStep3, which means calcStep3 must be called async, and we must await that as well in calcStep2, and all the way up the chain, even synchronous functions must be labeled async and awaited.

In this simplified example, it would not be so offensive to do that. But in reality, my algorithm is far more complicated, with a much deeper call stack involving many class methods, iterations, etc. Is there a better way to manage awaiting functions in this type of scenario? Other tools I can use, like generators, or event emitters? I'm open to simple solutions or paradigm shifts.

Aucun commentaire:

Enregistrer un commentaire