0

I'm trying to think of the best way to format the code of a complicated decision tree while using javascript promises.

I've read the following questions but couldn't find what I'm looking for:

My desires:

  1. The flow must be very easy to understand to new developers who'll enter the project in the future.
  2. Every action/step will be handled in an isolated function/promise, and could be replaced easily, and without having to test other steps again.
  3. The input should flow from each promise to the next.

A simple decision tree for example: example decision tree

I thought about the following methods:

Method 1

var path = "";
var input = {hhh:111};

step_1(input)
  .then(step_2)
  .then(change_path_to_A_or_B_according_to_input)

    .then(step_a1)
    .then(step_a2)
    .then(change_path_to_X_or_Z_according_to_input)

      .then(step_x1)
      .then(step_x2)

      .then(step_z1)

    .then(step_b1)
    .then(step_b2)
    .then(step_b3);

In this method, each step or junction will first check the path variable and then decide if it should run or not. Changing the path is relatively hard, because the content of the steps should change according to their location in the decision tree (i.e. the path variable's examination should be adjusted). However, it quite easy to understand the decision tree by looking at it, although the indentation is manual and doesn't really have any effect.

Method 2

var input = {hhh:111};

step_1(input)
  .then(step_2)
  .then((input) => {
    if(condition) {
      return
        step_a1(input)
          .then(step_a2)
          .then((input) => {
            if(condition) {
              return
                step_x1(input)
                  .then(step_x2);
            } else {
              return
                step_z1(input);
            }
          });
    } else {
      return
        step_b1(input)
          .then(step_b2)
          .then(step_b3);
    }
  });

In this method, changing the path is relatively easy, because only the tree itself should be adjusted. However, it's less readable.

Any better suggestions?

Danilo
  • 1,017
  • 13
  • 32
A-S
  • 2,547
  • 2
  • 27
  • 37
  • 1
    Just use `async`/`await` and the code will wait for each Promise to complete before proceeding. That way you can use simple if/else code without callbacks. –  Sep 21 '19 at 09:57
  • First, thanks for your suggestion. Second, you should add your suggestion as an answer, not a comment (and I'd thank a code example as well). Third, your suggestion lacks some basic Promise benefits like an error handling to the entire tree, so I'll wait for some other answers first. – A-S Sep 21 '19 at 10:01
  • 1
    I very rarely post actual answers here because 95% of JS questions are duplicates where people simple didn't find the answer because they were searching for the wrong thing (or not at all). The fact that you're using a decision tree is irrelevant here; the key concept is how to wait for a promise before proceeding and thus write "synchronous" code. Which is the entire reason async/await was added to JS two years ago or whenever. Here's working code that includes error handling: https://jsfiddle.net/khrismuc/wzj0a6f8/ –  Sep 21 '19 at 10:12
  • Hey man, great stuff, I'll do further testing, but it looks like you've solved it. An answer to duplicated questions gets you downvotes? If you'll add it as an answer - I'll mark it as the answer... – A-S Sep 21 '19 at 11:31
  • 1
    It should be fairly simple to construct a specialised language for this particular job - [for example](https://jsfiddle.net/kcf4ueom/). Trouble with this kind of thing is that: (a) it might be too specialised - ie it may not allow for all future eventualities. (b) it's something new for you and others to learn. In its defence, this example is pretty simple to learn and allows you to "break out" of the straightjacket by chaining a standard `.then()` and/or `catch()` if/when necessary because `chain()` and `chain().branch()` both return Promise. – Roamer-1888 Sep 22 '19 at 17:56

1 Answers1

2

Method 1 is a linear promise chain, its indentation is arbitrary and does not necessarily relate to your internal flow of control.

Method 2 is the correct way to go - that's how you do conditions in a promise chain. For better readability, use either a different indentation style and ternary operators

step_1({hhh:111})
.then(step_2)
.then(input => condition
  ? step_a1(input)
    .then(step_a2)
    .then(input => condition
      ? step_x1(input)
        .then(step_x2)
      : step_z1(input)
    )
  : step_b1(input)
    .then(step_b2)
    .then(step_b3);
);

or use async/await syntax:

var input = {hhh:111};
input = await step_1(input);
input = await step_2(input);
if (condition) {
    input = await step_a1(input);
    input = await step_a2(input);
    if (condition) {
        input = await step_x1(input);
        input = await step_x2(input);
    } else {
        input = await step_z1(input);
    }
} else {
    input = await step_b1(input);
    input = await step_b2(input);
    input = await step_b3(input);
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Can this `input = await step_b3(input);` be shorten? I thought of this: ```javascript function doStep(step) { step(input); // input is global var, accessible from any location } ``` Is there a better way? – A-S Sep 21 '19 at 18:21
  • @A-S I'm not exactly sure what kind of data you need to pass around between the steps - it just seemed to be what your `.then()` chain was doing. If all of your functions operate on some kind of global state, you could of course simplify to `await step_b3();` – Bergi Sep 21 '19 at 18:27
  • 1
    @A-S, it looks like @Bergi has correctly understood your requirement here but one aspect is not addressed (either in the question or the answer); namely whether the decisions `a/b` and `x/z` can/should be made at the time of promise chain construction, or whether they can/should be made as the chain settles. The code in these two cases will be very different. Just so you are aware. – Roamer-1888 Sep 22 '19 at 11:36
  • @Roamer-1888, can you please elaborate about the differences? – A-S Sep 24 '19 at 18:14
  • 1
    @A-S, I can't give proper examples without writing an answer, however here's something that should help ... – Roamer-1888 Sep 24 '19 at 19:36
  • 1
    (1) If decisions can be made at the time of promise chain construction, you will end up with code that creates a chain programatically to give an expression that reflects a particular path through your tree such as `step_1().then(step_2).then(step_a1).then(step_a2).then(step_z1)`; the path through the tree is thus fixed before it starts to settle. – Roamer-1888 Sep 24 '19 at 19:36
  • 1
    (2) If decisions can be made only as the chain settles, you will end up with something like Bergi has given you in his answer; the chain reflects all possible paths through the tree but the particular path taken is decided on as the chain settles. – Roamer-1888 Sep 24 '19 at 19:37