DOUG STEVENSON: Hey, everyone.
Thanks for joining me for this Firecast.
In the prior miniseries of three videos,
I talked about how to use JavaScript promises to manage
your asynchronous work running on Cloud Functions.
But I also mentioned a couple of times
that TypeScript and ECMAScript 2017
support a syntax called async/await that gives you
a cleaner way to express sequences of asynchronous work
and handle any errors that may occur with that work.
If you don't already understand JavaScript promises,
be sure to go back and watch those three videos,
because you need to understand how promises work in order
to understand async/await.
OK.
The first thing to know is that there
are two keywords that work together called,
unsurprisingly, async and await.
Let's take a look at a sink first--
I mean, async.
You use the async keyword to declare a function that always
returns a promise that should resolve when some background
work is complete, and you use the await keyword
to pause the execution of an async function
until some other promise is fulfilled or rejected.
There's quite a bit more to this,
and I think it's easier to understand
by looking at some TypeScript code in VS Code.
So let's do that.
Here's a new Cloud Functions project
configured to use TypeScript.
First, I'll create a new TypeScript file to play around
with, and I'll create a plain function in here
that returns a string.
When I hover the mouse over the function name,
the TypeScript compiler is correctly
inferring that this function returns a string, even if I
didn't declare it specifically.
I can be explicit about the return type
by adding it to the function declaration.
The function works the same with or without it.
Now, if I put the async keyword in front of this function,
the TypeScript compiler complains
about the return type.
It's saying that async functions must return a promise.
If I get rid of the return type, then
hover the mouse over the function name again,
I see the compiler has now inferred
that this function returns a promise that contains a string,
and there's no compiler errors.
Now, you might be wondering why there's no errors here.
The function internally returns a string,
but the function actually returns a promise
to any code that calls it.
The types don't match.
This is because the async keyword is forcing the function
to return a promise anyway.
What you don't see here is the fact
that TypeScript is automatically going
to wrap up that string into a promise at runtime,
and that promise is immediately fulfilled,
with the string as a result.
Let's look at it another way.
The async function you see here returns a promise to the caller
even though internally it returns a string.
I can manually wrap the string into a promise
with the promise.resolve function.
This actually behaves exactly the same way as before,
except now I'm being explicit about the promise wrapper.
And if I want to be perfectly clear,
I can also formally declare that this function returns a promise
that contains a string.
And it doesn't matter if the internal return
value is actually a promise.
Love you, TypeScript.
So with the async keyword added before a function,
you can indicate that the function always returns
a promise, no matter what it returns inside the function.
And the other thing the async keyword
does for a function is let you use the await keyword
inside that function.
With a weight, you can--
with await, you can temporarily pause the execution
of an async function until some other promise is
fulfilled or rejected, then get a hold of its result.
Let's take a look at that.
Here's the same async function as before
that returns a promise that's immediately
fulfilled with a string value.
Now I'll add another function called
getRank that returns a promise that's immediately
fulfilled with the number 1.
If I want to call getRank from within my function,
I can do that here and capture its promise in a constant.
VS code shows that the type of the constant
is a promise that contains a number,
and if I want to get a hold of the value fulfilled
by the promise, all I have to do is
use await before that promise, then
assign another constant to the value it returns.
Now, VS Code is showing us here that rank
is a number which is automatically
unwrapped from the promise.
Then I can work with that number without having
to call then on the promise like I would without async/await.
It turns out that the constant rank promise is actually
unnecessary, so I'll just get rid of it
and use await on the results of getRank directly.
This style is common, and it's similar to chaining promises
that I showed in a previous video.
With async/await, you can deal with promises
without having to work with promise objects directly.
They wrap and unwrap values from promises automatically,
which is really convenient.
But there's one more thing--
remember that promises can be rejected with an error.
So how can you capture these errors when using async/await?
For that you can use a try-catch block,
which is very similar to try-catch in Java and Swift.
Anytime you use await to unwrap a rejected promise,
it'll throw an exception.
So you'll need to catch it if you want to handle that error.
Here, I'm adding a try-catch block
around the use of await to trap any rejected
promises during getRank, then return a different string
in case that happened.
If getRank returns a rejected promise,
the catch block will get executed with the value
from the rejected promise.
For most APIs, this will be an error object,
but here it's a string.
Now, if I modify getRank to return a rejected
promise like this, the string returned from my function
would indicate an error.
Don't worry, Firebase, you'll always be number one to me.
There's just one final thing you need to know about async/await.
The await keyword is only valid to use
within the body of an async function.
Anything else is a compiler error.
Let's see that.
If I remove the async keyword from my function,
you can see that VS Code immediately
marks the await keyword with an error.
If I hover over it, I get a message
from the TypeScript compiler saying
that await is only allowed within an async function.
Async and await must go together.
Only async functions have the capability
of being paused by await, which is why the compiler complained
in my example.
You can't just pause any old function with await.
The TypeScript compiler gives them special treatment,
particularly when transpiling down
to a version of JavaScript that doesn't support async/await.
All right, here's a summary of what you
need to know about async/await.
First, functions marked with the async keyword always
return a promise.
Second, if the return value from inside an async function
is not a promise, it will be wrapped in one
that resolves immediately.
Third, the await keyword will pause
the execution of an async function
until a promise is complete.
Fourth, await will do one of two things
with the result of a promise.
It will either give you the result from a fulfilled promise
or throw an exception from a rejected promise.
Fifth and last, await is only valid for use
inside an async function.
I'm sure you've noticed that the examples I showed
you aren't very practical, but now you
should have an idea about how async/await is structured.
In the next video, I'll show you how you can use async/await
with Cloud Functions by converting those Boston weather
functions from my previous videos.
I promise you, you won't have to await very long for it.
So be sure to subscribe right here to the Firebase channel
on YouTube to get notified when that's available,
and I'll see you then.
[MUSIC PLAYING]