Programming with APIs

9.1 Promises

# Promises

A Promise, as the name implies is something that Promises a return value. We just don't know when we will get the reply. It could be fulfilled or it could fail to be fulfilled. Either way you will get an answer.

A Promise that is fulfilled and returns what we want is called resolved.

A Promise that is not fulfilled is called rejected.

The syntax for a Promise is just like thee fetch method. (That is because a fetch IS a Promise.). when you call fetch() you are actually given a Promise object which eventually will give you a response object and call the first then method or it fails and calls the catch method.

//version one that runs as soon as the current main stack is finished
let p = new Promise(function (resolve, reject) {
  resolve('Booya');
});
p.then(function (str) {
  console.log(str); //outputs `Booya`
}).catch(function (err) {
  //runs if the Promise function or the then function throws an error
});

//version two with a minimum 2 second delay
let delay = new Promise(function (resolve, reject) {
  setTimeout(resolve, 2000, 'hello');
  //wait for two seconds and then call the resolve
});
//at this point in your code delay is `pending` - not resolved or rejected
delay.then(function (response) {
  //response will be  the value returned by the resolve method inside the Promise
  console.log(response);
  //it will output 'hello'
});

delay.catch(function (err) {
  //if the reject method was called first from the  Promise
  //then this catch method's function would  run.
});
//this last line will run before ANY of the other console.logs
console.log('wait for it...');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

The then and catch can be added to the variable holding the Promise separately, or they can be chained together with the catch at the end of the chain.

# then() and catch() and finally()

When you create a Promise object you can chain onto it one or more then() methods. At the very end of the then() chain, you can put a catch() method.

new Promise(func1).then(func2).then(func3).then(func4).catch(func5);
//or
let p = new Promise(func1);
p.then(func2).then(func3).then(func4).catch(func5);
//notice that we can just put the names of the functions to call inside each
1
2
3
4
5

Just like the Promise object itself needs a function and that function will resolve or reject, each of the then() method calls plus the catch() method call needs a function.

If the function inside a then() method runs with no errors, then its return value gets passed along to the next then() method in the chain. The first return value becomes an argument for the second then(), and so on.

If ANY of the functions inside ANY of the then() methods throws an error, then the Error object will be immediately passed to the catch() method and the function inside the catch will run using the Error as its argument.

# resolve() and reject()

It is possible to create a Promise that resolves or rejects immediately. However, since Promises are asynchronous it means that the current stack finishes first. The then() method, which reads the value from the resolved or rejected promise is called asynchronously.

The Promise.resolve() method returns a Promise object that has been resolved.

The Promise.reject() method returns a Promise object that has been rejected.

let myGoodValue = 42;
let myBadValue = new Error('bad stuff');

let good = Promise.resolve(myGoodValue);
let bad = Promise.reject(myBadValue);
1
2
3
4
5

# Promise.all() and allSettled()

If you have multiple asynchronous tasks to accomplish and you want to wrap them together as a Promise you can do that. As an example - you want to fetch remote data from 4 sources or you want to open 4 files from the filesystem. Either of those things take an unknown amount of time and depend on factors that are out of your control.

We can use Promise.all() or Promise.allSettled() to achieve this. Both of these methods take an Array of Promise objects and then return an Array of results to your then() method.

The difference between them is that all() will call the then() when every one of the Promises has a result. You don't know if the results are all resolved, all rejected, or a mixture. With allSettled the then() method gets called only if all the promises in the Array were resolved. As soon as one of the Promises in the Array is rejected, the catch gets called.

So, it is a question of how many results do you need. If you ask for four things to work but are ok with only two or three actually working, then go ahead and use all.

If your app needs all four to have been successful, then use allSettled.

//creating a couple promises that will randomly resolve or reject
let p1 = new Promise((resolve, reject) => {
  //roughly half of the time this resolves
  let num = Math.random();
  if (Math.round(num)) {
    resolve(num);
  } else {
    reject(num);
  }
});
let p2 = new Promise((resolve, reject) => {
  //roughly half of the time this resolves
  let num = Math.random();
  if (Math.round(num)) {
    resolve(num);
  } else {
    reject(num);
  }
});

//test the Promise.allSettled method
Promise.allSettled([p1, p2])
  .then((results) => {
    //results is the array of numbers that resolved
    console.log(results[0], results[1]);
  })
  .catch((err) => {
    //first failure triggers this
  });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# Promise.race() and any()

Promise.race() and Promise.any() are very similar. They are both race conditions. They are both looking for a first Promise to be completed. They both need an array of promises to be passed in.

The difference is that race is looking for the first one to complete, regardless whether it is resolve or reject, and any is looking for the first successful result. The any approach will only run the catch if all of the promises in the array are rejected.

let p1 = new Promise((resolve, reject) => {
  reject(1);
});
let p2 = new Promise((resolve, reject) => {
  resolve(2);
});
//race gives us the first result back... good or bad
Promise.race([p1, p2])
  .then((response) => {
    console.log('First one back was successful');
  })
  .catch((err) => {
    console.log('First one back was rejected');
  });

//any only runs the catch if all the promises failed
Promise.any([p1, p2])
  .then((response) => {
    console.log('First successful result is back');
  })
  .catch((err) => {
    console.log('no successful results');
  });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# Error Handling with Promises

When you have a Promise followed by chain of then methods an error could occur at any point in that chain of methods. If it does, then the error will be passed to the catch at the end of the chain.

Best Practice

You should always put a catch at the end of your promise chain.

In NodeJS, not doing this will cause an error.

let p = new Promise((resolve, reject) => resolve(42)); //resolve immediately

let res = p
  .then((result) => {
    return result; //pass a value to then next then()
  })
  .then((result) => {
    throw new Error('A Cool Error'); //this will trigger the catch()
    return result; //pass a value to the next then() this doesn't happen cuz of the error
  })
  .then((result) => {
    return result; //final one would pass the value to `res` this doesn't happen
  })
  .catch((err) => {
    //error object ends up here in `err`
  });
//the variable `res` will still be a `pending Promise` at this point in your code
//then() is an async method
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

With the async await keywords the function pauses and waits for a promise to finish resolving or rejecting. This includes any then.

function async doSomething(){
  let result = await someFunctionThatReturnsAPromise().then(result=>{
    return result; //pass to then next then()
  }).then(result=>{
    return result; //actually pass the result to `result`
  });
  //now because of the await, `result` will have the return value from the 2nd then()
  console.log(result);
}
1
2
3
4
5
6
7
8
9

You can also use a standard try catch control-flow structure to handle errors coming back from functions.

try {
  doSomething(); //if an error happens in that function, the catch is triggered
  doSomethingElse(); //if an error happens in that function, the catch is triggered
  throw new Error('Sample Error'); //this will make the catch run too.
} catch (err) {
  //this runs if an error happens in the try block
}
1
2
3
4
5
6
7

So, if you had a call to a function that threw an error you can handle it with the catch part of a try catch block. It works just like the catch() method from a Promise.then().then().catch() chain.

# What to do this week

TODO

Things to do before next week.

  • Read all the content from Modules 9.1, 9.2, and 10.1.
  • Continue working on the Hybrid Exercises
Last Updated: 5/31/2023, 8:15:38 PM