Whenever you see someFunction(...).then(...)
(And may be followed with .catch(...)
To handle errors)
That means:
someFunction
returns aPromise
then(...)
handles the promise once it is fulfilled (orresolve
d)catch(...)
handles the error (if one occurs during the promise) (e.g. when it isreject
ed)
This happens in the background. (In technical terms, "it runs asynchronously")
So for example, if you had:
// Say time is a number, action is a function
function delay(time, action) {
// this is how you "roll your own" promise from scratch
// under the hood, the fetch function from your assignment creates a promise much like this
return new Promise(function(resolve, reject) { // worth noting you can name resolve and reject anything, but typically with a Promise, these are just called resolve and reject
// Wait time milliseconds then run the action function
setTimeout(function() { // the setTimeout function just delays a function by some time in MS;
// all JS implementations have this
try {
let result = action();
resolve(result); // this triggers .then(...) if one is attached
} catch(error) {
reject(error); // this triggers .catch(...) if one is attached
// otherwise if an error happens and there's no .catch(...) you get
// a warning about an unhandled promise rejection
// or depending on the JS engine and version, the program may just crash, like a regular error
}
}, time);
});
}
You could then do this:
delay(5000, function() {
console.log('This happened after 5 seconds');
return "OK";
})
.then(function(result) {
console.log(result); // OK
})
.catch(function(error) {
console.error(error); // this doesn't happen in this case as the function above should never fail
});
Or (handling errors):
delay(5000, function() {
return "string".push(1); // Strings don't have a push function, arrays do
})
.then(function(result) {
console.log("This never happens! The above function is impossible!");
})
.catch(function(error) {
console.error(error); // "string".push is not a function
});
You may be thinking "Damn this is a lot" That's why fat arrows came in:
delay(5000, () => "string".push(1)) // still an error
.catch(error => console.error(error)); // "string".push is not a function
delay(1000, () => {
console.log("This happened after 1 second");
return "OK";
})
.then(result => console.log(result)); // OK
Note that promises run asynchronously (in other words, they run in the background, the technical term is they "don't block"):
console.log("This prints first");
delay(1000, () => console.log("This prints last"));
console.log("This prints second");
Promises didn't always exist. The alternative to handling "asynchronous" operations in JS was callbacks (as you see in setTimeout
, where it takes a function as a parameter, and often you will create Promises
using callbacks as in the example above; or you will use another Promise, as long as the function does not block.. otherwise the Promise is pointless, because the code will just stop AKA be "blocked" until it finishes anyways).
With callbacks, the above might look like this:
function delay(time, action, callback) {
setTimeout(function() {
try {
callback(null, action());
} catch(error) {
callback(error, null);
}
}, time);
}
Then you would do:
delay(1000, () => "string".push(1), (error, result) => {
if(error) {
return "Damn I got an error here: " + error;
} else {
// do whatever with the result
}
});
Might not look so bad, however, what if you wanted to run another function after this returns that also returns asynchronously (e.g. not immediately), using our delay function, well...
delay(1000, () => "Whatever", (error, result) => {
if(error) {
// won't get an error this time who cares
} else {
delay(2500, () => "Damn, this is getting...", (error, result) => {
if(error) {
// blargh...
} else {
delay(5000, () => "Really fucking ugly....", (error, result) => {
if(error) {
// croikey...
} else {
/// and so on....
}
})
}
})
}
});
// with the Promise version, it would look like this:
delay(1000, () => "Whatever")
.then(result => delay(2500, () => "Whoa this isn't..."))
.then(result => delay(5000, () => "Nearly as fucking bad..."))
Here's a repl with all the code above (pretty much) it will print it out to the console with delays and you'll be able to see how it all works.
You will notice it doesn't run in any coherent order. That's because it uses Promises (and callbacks). You could chain all of the promises together and it would run in a coherent order if you wanted (as in the last code example above example).
But basically, this is what you saw in your assignment:
fetch("https://handlers.education.launchcode.org/static/planets.json")
.then(function (response) {
return response.json();
});
Now, there is one last thing... async/await
You may have noticed writing out Promises and then
and catch
is pretty wordy.... well, you can write async function
s in JS:
async function() {
// you can now use the await keyword in this block
}
For example:
function() {
delay(1000, () => "OK!")
.then(result => console.log(result));
}
Could be written as:
async function() {
let result = await delay(1000, () => "OK!");
console.log(result);
}
Remember You can only use await
inside of an async function.... it does nothing otherwise.
Another thing to remember is async
functions always return a Promise
....
async function() {
return 5;
}
Is actually:
function() {
return new Promise((resolve, reject) => resolve(5));
}
This means if you had:
let cached;
async function tryToGetJSON() {
if(cached) return cached;
else {
let result = await fetch("https://my-website.com/some-api-endpoint-that-returns-json").json();
cached = result;
return result;
}
}
You actually have:
function tryToGetJSON() {
if(cached) return new Promise((resolve, reject) => resolve(cached));
else {
return new Promise((resolve, reject) => {
fetch("https://my-website.com/some-api-endpoint-that-returns-json")
.then(response => {
let json = response.json();
cached = json;
resolve(json)
})
.catch(error => {
reject(error);
})
})
}
}
And that is ES6 Promises
and async/await
in a nutshell