0

I'm a newbie and trying to figure out something in Javascript that should be simple. I have 2 functions let's say

function play1(){
  Promise.resolve()
    .then(() => put('A', 1000))
    .then(() => put('B', 1000))
}
function play2(){
  Promise.resolve()
    .then(() => put('C'), 1000)
    .then(() => put('D'), 1000)
}

I need a third function so that it executes sequentially A, B, C, D What I've tried so far with no luck:

function playAllSequentially(){
  
  Promise.resolve()
    .then(() => play1())
    .then(() => play2())
}

but this doesn't get the job done, of course I could do

Promise.resolve()
    .then(() => put('A', 1000))
    .then(() => put('B', 1000))
    .then(() => put('C', 1000))
    .then(() => put('D', 1000))

but that is not the idea

in case it matters the content of put() is

function put(text, duration){
    $('#txtRemarks').text(text);
    delay(duration);
}

Thanks in advance

Jose Diaz
  • 23
  • 3

4 Answers4

2

It sounds like delay returns a promise it fulfills after a period of time. But put is completely ignores that promise, so it doesn't wait. Similarly, play1 and play2 don't return anything, so there's no reason for whatever is calling them to wait either. You need to return the promise chain. (Separately, you have the closing ) in the wrong place in the calls to put in play2.)

See the marked changes (but keep reading):

function play1() {
    return Promise.resolve() // <=== return
        .then(() => put("A", 1000))
        .then(() => put("B", 1000));
}
function play2() {
    return Promise.resolve() // <=== return
        .then(() => put("C", 1000))  // <=== parens
        .then(() => put("D", 1000)); // <=== parens
}

function playAllSequentially() {
    return Promise.resolve() // <=== return
        .then(() => play1())
        .then(() => play2());
}

function put(text, duration){
    $('#txtRemarks').text(text);
    return delay(duration); // <=== return
}

playAllSequentially();

function delay(duration) {
    return new Promise((resolve) => {
        setTimeout(resolve, duration);
    });
}
<div id="txtRemarks"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

But, that code has the explict promise creation anti-pattern. You don't need those Promise.resolve() calls if put, play1, and play2 return promises:

function play1() {
    return put("A", 1000)
        .then(() => put("B", 1000));
}
function play2() {
    put("C", 1000)
        .then(() => put("D", 1000));
}

function playAllSequentially() {
    return play1()
        .then(() => play2());
}

function put(text, duration){
    $('#txtRemarks').text(text);
    return delay(duration); // <===
}

playAllSequentially();

function delay(duration) {
    return new Promise((resolve) => {
        setTimeout(resolve, duration);
    });
}
<div id="txtRemarks"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

But, here in 2022, that's not how I'd write this code. Instead, I'd use async functions:

async function play1() {
    await put("A", 1000);
    await put("B", 1000);   // Maybe a return here, if you want play1
                            // to return the result of `put`
}
async function play2() {
    await put("C", 1000);
    await put("D", 1000);   // See above
}

async function playAllSequentially() {
    await play1();
    await play2();          // See above
}

async function put(text, duration){
    $('#txtRemarks').text(text);
    await delay(duration);
}

playAllSequentially();

function delay(duration) {
    return new Promise((resolve) => {
        setTimeout(resolve, duration);
    });
}
<div id="txtRemarks"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Finally, in all of the above the functions never reject their promises, but real world functions usually can, so playAllSequentially (or whatever calls it) should handle rejection.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • should I remove my answer as it is so similar to yours and yours is potentially better, or should we let them coexist? I started writing mine way before you published yours, but then finished after word and they are very similar (because they solve the same problem, not because I was steeling your work) – Zachiah Oct 29 '22 at 11:57
  • @Zachiah - Yeah, I was held up by the misplaced `)` in the original code -- couldn't figure out why `C` and `D` were appearing one right after another in my live example! :-) I think it's fine for them to coexist. But I'd fix the thing about the `for` and `fetch` that FZs pointed out. – T.J. Crowder Oct 29 '22 at 12:08
  • Ok cool. I fixed the thing FZs pointed out I think (; – Zachiah Oct 29 '22 at 12:10
1

You need to make sure all the functions play1, play2 etc to return promises, so that the then chain waits for returned promise's resolve callback.

function play1(){
  return Promise.resolve()
    .then(() => put('A', 1000))
    .then(() => put('B', 1000))
}

Promise.resolve()
  .then(() => play1())
  // Since play1() returns a promise, following `then` would wait for it's resolve callback
  .then(() => ...)
Wazeed
  • 1,230
  • 1
  • 8
  • 9
1

The problem with your code is that your functions aren't returning the Promises. You have 2 really easy fixes here:

1. Return the Promises manually

All you need to do is something like:

function play1(){
  return Promise.resolve()
    .then(() => put('A', 1000))
    .then(() => put('B', 1000))
}

function play2(){
  return Promise.resolve()
    .then(() => put('C'), 1000)
    .then(() => put('D'), 1000)
}

Also presumably you need to return delay as well in put depending on how delay works

function put(text, duration){
    $('#txtRemarks').text(text);
    return delay(duration);
}

Summary

You should always be returning your promises or else you will end up with tons of hanging promises in memory that may or may not get executed when you want them to.

Example

In this code:

const fn = () => {
    const promise = fetch("https://some-url");
    for (let i = 0; i < 1000000000000; i++) {
        doSomeExpensiveTask();
    }
}

The promise isn't going to resolve before the for loop. The promise should get resolved after all of your imperative code but maybe not as there might be a lot of repainting to be done or something. The only way to know when your promises are resolved is by using the patterns mentioned.

2. Use async await

The more idiomatic way of doing this in javascript is to rewrite your functions as async functions and then await the promises

async function play1(){
  await put('A', 1000);
  await put('B', 1000);
}

async function play2(){
  await put('C');
  await put('D');
}

The delay function:

async function put(text, duration){
    $('#txtRemarks').text(text);
    await delay(duration);
}

Then you could change your usage to be:

async function playAllSequentially() {
    await play1();
    await play2();
}

although returning would also work here. You can mix and match these patterns as the async/await is just syntactic sugar for what I showed before.

Zachiah
  • 1,750
  • 7
  • 28
  • "The actual fetching isn't guaranteed to be done at any specific time, ..." - this is not true. `fetch`ing will start immediately, even if you don't do anything to the promise. The `for` loop is synchronous, so it will block the code and that's why the promise won't receive the result until the `for` loop ends (in fact, it's impossible for the promise to resolve before the end of `for`), but the request will be sent immediately. The statements about the need to always return Promises are correct, but that example is not fitting. – FZs Oct 29 '22 at 12:05
  • @FZs Thanks you are right. I learned promises by doing not by reading the spec so my knowledge is sometimes ever so slightly off lol. I tried to update it to more closely reflect reality. Let me know if it's accurate now. – Zachiah Oct 29 '22 at 12:09
  • 1
    Zachiah & @FZs - Just a minor note: It's entirely possible for the promise to resolve (more accurately, *be resolved*) during the `for` loop, if synchronous code running in the loop resolves it. In fact, it could even settle (which is different from resolving). What's impossible is *observing* that settlement synchronously. (Re "resolve" vs. "settle" vs. "fulfill" vs. "reject," see my blog post about promise terminology [here](https://thenewtoys.dev/blog/2021/02/08/lets-talk-about-how-to-talk-about-promises/).) – T.J. Crowder Oct 29 '22 at 12:15
  • @T.J.Crowder So I should have added a `.then` and then said that the `.then` code would not be executed until afterwords? – Zachiah Oct 29 '22 at 12:24
  • 2
    @Zachiah - I'd just go with "you can't observe promise settlement during the `for` loop" (or even just leave it as it's fairly pedantic, though I'm always bothered when I see "resolve" used incorrectly -- it's rampant, though). Mostly it's just since you and FZs were already discussing it, I thought I'd pitch in with a clarification. (And all of the above said, given it's a network operation, it's extremely unlikely to settle *during* the `for` loop. :-D ) – T.J. Crowder Oct 29 '22 at 12:31
  • 2
    @T.J.Crowder re: "the promise may fulfill during the `for`, it just can't be observed" - I did know that, I was just speaking loosely. But, I didn't know that *resolve* and *fulfill* mean different things, I've always used "*resolve*" to mean "*fulfill*". Thanks for sharing that! – FZs Oct 29 '22 at 12:56
  • 1
    @FZs - Yeah, sorry, I should have realized you did. :-) Glad the other info was helpful! :-) – T.J. Crowder Oct 29 '22 at 13:01
0

I executed the script and the errors are two.

  1. You called the function delay which is undefined in JavaScript, maybe you refer to setTimeout().
  2. You defined the function put with two parameters, but here: put('C'), 1000) put('D'), 1000).

You called put with 1 parameter, in this example 'c' and 'd'. So the answer is yes, playAllSequentially() works if you fix these errors.

In my case I executed the code with Node.js and works correctly.

Meet Bhalodiya
  • 630
  • 7
  • 25
  • I think He was just saying that `delay` was defined elsewhere. Sometimes (actually often), people give code that relies on functions that they didn't provide. It isn't ideal, but it does happen a lot. – Zachiah Oct 29 '22 at 12:12