1

I need to run two (or more) lines of code in sequence in vanilla javascript.

Each invokes calls to routines (aka top level functions, which have a purpose of performing procedural steps rather than calculating a result). These in turn call multiple others.

function fMasterProcedure() {
    fSubProcedure1();
    fSubProcedure2();
    ... etc
}

Suppose the code for fSubProcedure1() is as follows (for this illustration):

function fSubProcedure1() {
    fSubSubProcedure1_1();  // Normal
    fSubSubProcedure1_2();  // Async
    fSubSubProcedure1_3();  // Normal
    fSubSubProcedure1_4();  // Async
    fSubSubProcedure1_5();  // Normal
}

This is literally the same question as here how to run sequence functions in javascript, although the answers provided there do not show how to do it, and there have been syntax updates in the years since in relation to promises and asynchronous functions.

These days the answer seems to be promises and async/await... or is it just callbacks without using promises?

I have read many descriptions and tutorials on promises, including here.

However, I end up with code like this:

function fMasterProcedure() {
    return (new Promise(function(resolve, reject) {
        fSubProcedure1();
        setTimeout(function () {
            resolve();
        }, 0);
    })).then(function(result) {
        fSubProcedure2();
    });
}

As you can see, it adds 400% more lines unnecessarily and still doesn't work reliably.

Is there a more efficient way to write this, or a more appropriate way?

(The requirement is to run fSubProcedure2 after fSubProcedure1 (and any of its child processes) are completed in full. I am trying to move from "that's not how JS works" to "here's how to make JS achieve that requirement").

I'm trying to get the good understanding so I can apply it to a whole project approach - there are many examples within, of each.

ed2
  • 1,457
  • 1
  • 9
  • 26
  • 1
    Have you looked into "Continuation Passing Style?" – Robert Harvey Aug 23 '20 at 16:28
  • 2
    Your original code should work just fine, unless `fSubProcedure1` does something asynchronous. If it does something asynchronous, please post the code of `fSubProcedure1` so we can show how it can be chained properly – CertainPerformance Aug 23 '20 at 16:28
  • @RobertHarvey Thank you, it seems relevant but I am unable determine explicit examples of how to apply to this situation. – ed2 Aug 23 '20 at 16:37
  • "*These days the answer seems to be promises and async/await...*" - yes indeed. Have you tried this approach? Do `fSubProcedure`s return promises? – Bergi Aug 23 '20 at 16:38
  • @CertainPerformance Thank you, it sometimes works 'just fine', other times not, depending on whether subroutines contain promises or async functions such as fetch etc. In any event, 'works fine' means every 1,000 lines of code becomes ~5,000 and the call stack grows way too deep which makes me nervous about dependencies and and memory management, and just doesn't seem to be good code. I was hoping I could re-write my example to be more efficient, especially once extended to "more than 2" lines of sequential code. – ed2 Aug 23 '20 at 16:40
  • @Bergi yes indeed... is that not what I have done above? – ed2 Aug 23 '20 at 16:41
  • If it returns a Promise, `fSubProcedure1().then(fSubProcedure2)`? – CertainPerformance Aug 23 '20 at 16:41
  • 1
    @ed2 No you didn't. All you did in your "400% more lines" snippet was to wrap an artificial `setTimeout(…, 0)` in a promise and defer the `fSubprocedure2` call until after that. If the subroutines do something asynchronous, they must return promises (rewrite their code if they don't). Then it's as simple as `async function fMasterProcedure() { await fSubProcedure1(); await fSubProcedure2(); }` – Bergi Aug 23 '20 at 16:44
  • @Bergi ok, thanks. I assume to rewrite as a promise I move `return (new Promise(function(resolve, reject) {` inside the top of fSubProcedure1, and end it with `resolve()`, then i can remove the `setTimeout`... – ed2 Aug 23 '20 at 16:57
  • 1
    @ed2 No, all you need to do is to make it `async` which causes it to automatically return a promise. You do never need the `new Promise` constructor unless you are interfacing with callback code. But all your subprocedures, and their subprocedures, should use `async`/`await` for asynchronous code. – Bergi Aug 23 '20 at 16:59
  • @Bergi Thanks, so I just replace `function` with `async function` in all my function declarations, and then list them in a master calling procedure with each subfunction called by `await`? That's it? Did I understand correctly? Thanks. – ed2 Aug 23 '20 at 17:10
  • 1
    That's it, provided your functions return promises, otherwise `await` will not wait that much ;-) – trincot Aug 23 '20 at 17:11
  • 1
    All this would be more interesting if you presented your concrete problem, with the asynchronous API you are using, ...etc. – trincot Aug 23 '20 at 17:11
  • 1
    @ed2 Yes, that's it, assuming the "*subroutines [that] contain promises or async functions such as fetch etc.*" also do return promises which don't fulfill before the async job is done. – Bergi Aug 23 '20 at 17:15
  • [Don't signal your edits in text.](https://rpg.meta.stackexchange.com/questions/3454/dont-signal-your-edits-in-text) – Robert Harvey Aug 23 '20 at 17:20
  • @trincot Thanks, the concrete problem is ye olde PEBKAC type problem - it's a conceptual thing that I am hoping to use to approach a lot of different specific instances of essentially the same thing: needing line 2 to not start until line 1 is done. In some cases line 1 contains async operations and in some cases not. But what you are saying makes sense. Thanks. – ed2 Aug 23 '20 at 17:26
  • @RobertHarvey Thanks, approach duly changed. – ed2 Aug 23 '20 at 17:30

1 Answers1

1

If fSubProcedure1 and fSubProcedure2 are two asynchronous functions and you wanna execute them one after the other then try to go for something like :

async function fMasterProcedure() {
    await fSubProcedure1();
    await fSubProcedure2()
}
// to execute the fMasterProcedure function
fMasterProcedure().then(console.log).catch(console.error);
//replace console.log and console.error with your appropriate callbacks

Example :

// returns Promise<number>
function fSubProcedure1() {
    console.log("runs first");
    return Promise.resolve(2*2);
}
// returns void
function fSubProcedureNormal() {
    console.log("runs second");
    //returns nothing
}
// returns number which gets converted to Promise<number>
async function fSubProcedure2() { 
    console.log("runs third");
    return 2*4;
}
async function fMasterProcedure() {
    let a = await fSubProcedure1();
    fSubProcedureNormal();
    let b = await fSubProcedure2();
    return `${a} : ${b}`;
}

fMasterProcedure().then(console.log).catch(console.error);

One important point... You would wanna try and catch error inside fMasterProcedure for every await

Resources :

async function

async await

Check out these resources to learn more

Gray Hat
  • 368
  • 2
  • 10
  • Thanks. What does line 3 do? If my fSubProcedure1 does not return anything, does line 3 make it into a promise... it seems the important thing I need is to be waiting for a promise, not so much to care what the returned result is rather than when it is returned if that sounds right... – ed2 Aug 23 '20 at 17:01
  • Should the subprocedure functions be declared as async like `async function fSubProcedure1() {` and `async function fSubProcedure2() {` in order to make them promises that the master procedure will wait for? Thanks. – ed2 Aug 23 '20 at 17:11
  • 1
    any function which is declared `async` or returns a `Promise` can be handled with `await` keyword. `await` can only be used in `async` functions. Also **It does't matter what your function returns. It will wait for the function to finish execution** – Gray Hat Aug 23 '20 at 17:40
  • 1
    I edited my answer. Hope that'll make things easier for you to understand. `async` just converts whatever return type to `Promise` and `await` is just used to wait for the `Promise` to resolve. – Gray Hat Aug 23 '20 at 17:50
  • Thanks @Gray Hat, I will test out your answer on my code and update this with the results once done. – ed2 Aug 24 '20 at 14:08