1

I am facing an issue while using promise to control async tasks in JavaScript. In the following code, I wanted to output 'first' before 'second'. I used promise then block, but it didn't work as I wanted.

I used setTimeout to create different time delay, so that I can control async tasks order no matter what. Can anyone please help me understand why the code is not giving the output I wanted?

Here is the code:

let first = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(console.log('first'));
  }, 30);
});

let second = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(console.log('second'));
  }, 3);
});

first.then(second);
Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
Sonet Adhikary
  • 466
  • 1
  • 4
  • 8
  • 1
    Once the promise is constructed, the train has left the station, so to speak... `first.then(...)` takes a callback function to be invoked when `first` has been fulfilled. It makes no sense to pass it a promise object. – Patrick Roberts Mar 07 '21 at 07:34
  • 2
    try `first.then(() => second);` – Brettski Mar 07 '21 at 07:34
  • Your real code does not actually use `setTimeout()` for anything - all you have is two asynchronous requests, and you want `first` printed before `second`, am I right? – Tomalak Mar 07 '21 at 08:06

4 Answers4

2

I don't believe that your code sample reflects your real situation, so I think it's not useful to solve the issue in your code sample.

Here is what I think you have:

// a task that returns a promise and takes some amount of time
const task = (name) => new Promise(resolve => setTimeout(() => resolve(name), Math.random() * 1000));

// a function using the task result
const taskDone = (name) => console.log(name + ' done');

// two instances of that task, finishing in unpredictable order
var action1 = task('task 1').then(taskDone);
var action2 = task('task 2').then(taskDone);

Run the snippet a few times to see that the result order changes. All you want a way to ensure that 'task 1 done' is always printed before 'task 2 done', even if task 2 finishes earlier.

The way to do this is to use Promise.all().

const task = (name) => new Promise(resolve => setTimeout(() => resolve(name), Math.random() * 1000));
const taskDone = (name) => console.log(name + ' done');

// set up tasks
var allTasks = [task('task 1'), task('task 2')];

// wait for results and evaluate 
Promise.all(allTasks).then(allResults => allResults.forEach(taskDone));

allResults will be in the same order as allTasks, so 'task 1 done' will now always be printed before 'task 2 done'.

If you only have two tasks, you can be more explicit:

Promise.all([
  task('task 1'),
  task('task 2')
]).then(allResults => {
  taskDone(allResults[0]);
  taskDone(allResults[1]);
});
Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • I do think this answer contains useful information, but you have to admit it reads like a politician's response: _"Here's the answer to what you **should** have asked..."_ – Patrick Roberts Mar 07 '21 at 08:34
  • @Patrick I don't believe in "answering the question as asked" as the only rule to how this site works. People typically ask *because* they don't know what they're doing. And *"I want promise results to be printed in 'code order'"* is common enough a problem for people struggling with the asynchronous code on a conceptual level to make the assumption I made. – Tomalak Mar 07 '21 at 08:41
  • Sorry I failed to express my sense of humor, I didn't mean that part of my comment as serious criticism. I just found it a bit funny was all. Like I said, it's a good answer. – Patrick Roberts Mar 07 '21 at 09:47
  • @Patrick Understood, no offense taken at all. My assumption could as well prove to be wrong, we'll see. :) – Tomalak Mar 07 '21 at 09:51
1

Wrap it to a function and create a closure and it will be called when you call it right the way.

let first = () => new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(console.log('first'));
    }, 30);
});

let second = () => new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(console.log('second'));
    }, 3);
});

first().then(second);
Ali Torki
  • 1,929
  • 16
  • 26
1

The first problem is that your promise constructor executor functions call console.log() before the promises are even fulfilled. Instead, you should wait to console.log() the values that promises are fulfilled with:

let first = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('first');
  }, 30);
});

let second = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('second');
  }, 3);
});

first.then((value1) => {
  console.log(value1);
});

second.then((value2) => {
  console.log(value2);
});

Now for the matter of sequencing the output, you can achieve this by awaiting the fulfilled value of second only after awaiting the fulfilled value of first, rather than awaiting both of them at the same time:

let first = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('first');
  }, 30);
});

let second = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('second');
  }, 3);
});

first.then((value1) => {
  console.log(value1);
  return second;
}).then((value2) => {
  console.log(value2);
});
Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
  • This is an important point: "...promise constructor executor functions call console.log() **before the promises are even fulfilled**." – Brettski Mar 07 '21 at 08:30
0

If you start two promises parallelly which are resolved by setTimeout then there is no way you can control your logs order.

You need to establish some order dependency between the two to resolve them in order.

One way could be as follows:-

let first = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(console.log('first'));
    }, 30);
});

first.then(() => {
    let second = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(console.log('second'));
        }, 3);
    });
    return second;
})
Diwakar Singh
  • 684
  • 1
  • 5
  • 19