Progress pill
Advanced JavaScript

Concurrency with Promises

JavaScript and NodeJS Fundamentals

Concurrency with Promises

  • .then()
  • Reject()
  • Chaining operations using Promises
A Promise is a built-in JavaScript object that represents a value that will be ready in the future.
We can create a Promise like this:
const promise = new Promise((resolve, reject) => { // Do something that takes time here... resolve("It worked!") // This means everything went OK })
The new Promise() part creates the promise.
Inside it, we give it a function with two parameters:
resolve is a function we call when everything is successful reject is a function we call if something goes wrong
In the example above, we just resolve it immediately with the message "It worked!".

.then()

To do something after the promise is done, we use .then():
const promise = new Promise((resolve, reject) => { // Do something that takes time here... resolve(100) // This means everything went OK }) promise.then(result => { console.log("The result is:", result) })
This prints:
The result is: 100
The value we passed to resolve() gets sent to the function inside .then() as result.
Let’s simulate a task that takes 2 seconds using setTimeout:
const delayedPromise = new Promise( (resolve, reject) => { setTimeout( () => resolve("Done waiting!"), 2000 ) }) delayedPromise.then(result => console.log(result))
This will wait 2 seconds and then print:
Done waiting!

reject()

Let’s create a promise that fails:
const failingPromise = new Promise((resolve, reject) => { reject("Something went wrong") })
Now if we use .then() on this, nothing will happen, because .then() only handles success.
To handle errors, we use .catch():
const failingPromise = new Promise((resolve, reject) => { reject("Something went wrong") }) failingPromise .then( result => console.log("This will NOT run:", result) ) .catch( error => console.log("Caught an error:", error) )
This prints only
Caught an error: Something went wrong
The value passed to reject() is sent to the .catch() function.
Let’s build a Promise that sometimes works and sometimes fails, based on some condition.
function checkNumber(n) { return new Promise((resolve, reject) => { if (n > 0) { resolve("Positive number") } else { reject("Not a positive number") } }) }
Now we can call this and handle both cases:
checkNumber(5) .then( msg => console.log("Success:", msg) ) .catch( err => console.log("Failure:", err) )
This prints:
Success: Positive number
And if we try with a different number:
checkNumber(-1) .then( msg => console.log("Success:", msg) ) .catch( err => console.log("Failure:", err) )
It prints:
Failure: Not a positive number

Chaining operations using Promises

We can rewrite our earlier example using Promise, and it will look much cleaner.
Let’s start by writing a new version of our doubling function, but this time, it returns a promise:
function doubleNumbers(numbers) { return new Promise(resolve => { // Wait 1 second before doing the operation setTimeout(() => { const doubled = numbers.map(n => n * 2) resolve(doubled) // Return the result using resolve }, 1000) }) }
Now we can use .then() to tell JavaScript what to do with the result:
function doubleNumbers(numbers) { return new Promise(resolve => { // Wait 1 second before doing the operation setTimeout(() => { const doubled = numbers.map(n => n * 2) resolve(doubled) // Return the result using resolve }, 1000) }) } const input = [1, 2, 3] doubleNumbers(input) .then( result => console.log("Doubled numbers:", result) )
This prints:
Doubled numbers: [ 2, 4, 6 ]
So far, this works the same as the callback version, but now the code is easier to extend and read.
Let’s say we want to add more steps:
  1. First, double all the numbers
  2. Then, remove numbers smaller than 4
  3. Finally, add them all together
We can write one function for each step, all using promises:
function doubleNumbers(numbers) { return new Promise(resolve => { // Wait 1 second before doing the operation setTimeout(() => { const doubled = numbers.map(n => n * 2) resolve(doubled) // Return the result using resolve }, 1000) }) } function filterBigNumbers(numbers) { return new Promise(resolve => { setTimeout(() => { const filtered = numbers.filter(n => n > 3) resolve(filtered) }, 1000) }) } function sumNumbers(numbers) { return new Promise(resolve => { setTimeout(() => { const total = numbers.reduce((acc, n) => acc + n, 0) resolve(total) }, 1000) }) }
Now we can chain them together like this:
const input = [1, 2, 3] doubleNumbers(input) .then(filterBigNumbers) .then(sumNumbers) .then( result => console.log("Final result after all steps:", result) )
This prints:
Final result after all steps: 10
Let’s walk through what this does:
  1. doubleNumbers doubles the array: [2, 4, 6]
  2. filterBigNumbers removes anything ≤ 3: [4, 6]
  3. sumNumbers adds the remaining numbers: 4 + 6 = 10
  4. Finally, we print the result.
Each .then() waits for the step before it to finish. So we can build a chain of actions without nesting. This makes the code more readable and easier to debug.