Download as pdf or txt
Download as pdf or txt
You are on page 1of 96

Callbacks, Promises, and

Async Functions
Lionel KITIHOUN
September 16, 2023
● Beginners in JavaScript

Audience ● Callback lovers

● Experienced developers (as a


refresher)
Objective

● Understand asynchronous callbacks.


● Introduce promises, and the problems they solve.
● Learn how to advantageously replace promise chains with async
functions.

3
● Slides

Materials ● Code snippets

● Quizzes
Procedure

5
Synchronous vs
asynchronous
Promises Recap, Questions, etc.
programming

I II III IV V

Callbacks Async functions

6
Notice

We won’t talk about:

● Threads
● Event loop (maybe a little 😉)
● Micro and macro tasks

7
Synchronous vs asynchronous
programming
Synchronous Programming

Almost everybody was introduced to programming with a synchronous


language.

In synchronous programming, instructions are executed sequentially, in the


order they appear in the code (or functions).

Here are some examples.

9
Search Tags in Text

function findTags(text: string, tags: string[]) {


const matches: string[] = []
for (const tag of tags) {
if (text.includes(tag)) {
matches.push(tag)
}
}
return matches
}

10
Search Tags in Text II

function findTags(text: string, tags: string[]) {


return tags.filter(tag => text.includes(tag))
}

11
Database Connection

<?php
$dsn = 'mysql:dbname=otakudb;host=127.0.0.1';
$cn = new PDO($dsn, 'buu', 'chocolate');
$query = 'select title, author, hero from mangas';
$rs = $cn->query($query);
foreach ($rs as $row) {
echo $row->title, $row->author, $row->hero;
}
$cn = null;
?>
12
Reading Lines from a File

ifstream in ("input.txt");
string line;
while (getline(in, line)) {
cout << line << endl;
}
cout << "No more line to read..." << endl;

13
The “Problem” with Synchronous Programming

With synchronous programming, before our program move to the next


instruction, the current instruction need to be executed.

The “problem” is that long running functions will block your program and it
can get stuck at some point, not enable to do something else.

This operating mode is not really suited to environments where


responsiveness is important and maximum CPU usage is desired.

14
Example: Open a Big File in an Editor

Maybe some of you have experienced it before. When you try to open a really
big text file in an editor, the editor freezes for a long time.

That’s because the program must wait until the entire contents of the file have
been loaded.

15
Blocking Code

Opening and reading data from a file, creating a connection and sending
queries to a database, downloading a file, are usually delegated to the OS
kernel which notifies the program once they are completed.

Which means that during the time these operations are handled by the kernel,
our program does nothing but wait.

16
Real-World Illustration

Imagine you are a young graduate and you want to continue your studies at a
top university in France or the USA. You submit your application to Campus
France for example.

Applications take six to nine months to process. What will do during this time?

17
Source: https://www.talkspace.com/blog/stop-procrastinating-how-to/
18
Source: https://stock.adobe.com
19
The World Is Async by Nature

When you order food on Gozem, or a new outfit from your tailor, you are busy
with other activities. When your order is ready they call you, or a delivery man
brings it to you.

You don’t waste your time on tasks that don’t fit your skills. You delegate them
to other people more qualified to do it, and you just want the result back.

That how the world is made. And some programming languages have
adopted this behaviour.
20
Asynchronous Programming

Asynchronous programming is a technique that enables your program to start


a potentially long-running task and still be able to be responsive to other
events while that task runs, rather than having to wait until that task has
finished. Once that task has finished, your program is presented with the
result.

Definition from MDN

21
https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/

22
Asynchronous Programming with JS

JavaScript, with the asynchronous programming paradigm, allows to:

1. Start a long-running operation by calling a function.


2. Have that function start the operation and return immediately, so that our
program can still be responsive to other events.
3. Notify us with the result of the operation when it eventually completes.

Historically, JS used callbacks for the notification mechanism.

23
Opening a File in Node

import { readFile } from 'node:fs'

readFile('/path/to/file', (err, data) => {


if (err) {
console.error('Unable to read file', err)
return
}
console.log(data)
})

console.log('This will displayed before the file content...')


24
Callbacks

25
Definition

A callback function is a function passed into another function as an argument,


which is then invoked inside the outer function to complete some kind of
routine or action.

26
Not All Callbacks Are Asynchronous

It is important to understand when and how your callback function will be


called, since depending on the API you are using, your callback can be called
synchronously or asynchronously.

27
Synchronous vs Asynchronous Callbacks

function square(n: number) { function handleClick() {

return n ** 2 // ...
}
}

const btn = document.querySelector('.btn')


const numbers = [1, 5, 8, 12, 31]
btn.addEventListener('click', handleClick)
const squares = numbers.map(square)
console.log(squares)

28
Synchronous Callbacks

These kind of callbacks are helper functions generally used as subroutine to


perform a task.

They are called immediately without delay by the function they are passed to.

29
Examples of Synchronous Callbacks

The callbacks passed to the following methods are called synchronously.

● Array.prototype.sort
● Array.prototype.filter
● Array.prototype.every

30
Array “filter” Method Example Implementation

function filter(cb) {
const selected = []

for (const item of this) {


if (cb(item)) {
selected.push(item)
}
}

return selected
} 31
Asynchronous Callbacks

On the other hand, the call of an asynchronous callback is deferred to some


time after the end of the function it was passed to.

32
Async Callback Illustration

function asynchronousFunction(..., cb) {


// Here the API function does its job.
// And then, the call to your callback is scheduled...
callbackQueue.push(cb, ...args)
}

33
https://medium.com/gradeup/asynchronous-javascript-event-loop-1c8de41298dd
34
Examples of Asynchronous Callbacks

Usually, your callback is called asynchronously when your are dealing with
timers and HTTP requests.

● setTimeout
● XMLHttpRequest
● Most web APIs

35
https://www.freecodecamp.org/news/synchronous-vs-asynchronous-in-javascript/
36
First Quiz

37
Answers to The Quiz

Source: pinimg.com
38
Database access (Node)

const sqlite3 = require('sqlite3')


const db = new sqlite3.Database('/path/to/db.sqlite')
db.on('open', () => {
db.all('select * from mangas', (err, rows) => {
if (err) {
console.log('Error fetching items', err)
} else {
for (const row of rows) {
process(row)
}
}
db.close()
})
})
39
Conventions for Asynchronous Callbacks

Usually, asynchronous callbacks take two or more arguments.

● The first is an error object, used to indicate if everything went well.


● The second argument is the result of the operation carried by the lib
function.

40
Usual syntax

asynchronousFunction(..., (err, result) => {


if (err) {
// Something went wrong. Take appropriate action.
}
// We can do what we want this result here...
})

41
The “Problem” with Callbacks

It can happen that a lot of callbacks get nested, making the code hard to read
and maintain.

As an example, imagine that we want, for each manga in our database, to list
its characters, and for each character, to display his skills.

42
Nested Callbacks Illustration

db.on('open', () => {
db.all("select * from mangas where type = 'Shonen'", (err, mangas) => {
if (err) {
console.log('Error fetching mangas', err)
return
}
mangas.forEach(manga => {
const { id } = manga
db.each('select * from characters where manga_id = :id', { id }, (err, c) => {
db.all('select * from skills where character_id = :cid', { cid: c.id }, (err, skills) => {
// Work with the character and his skills
})
})
})
})
})
43
Callback Hell 😈

At the bottom of this code snippet, there is this nice looking succession of
closing brackets.

This lovely figure is called a callback hell or pyramid of doom and happens
when a lot of callbacks are nested.

This kind of code was common in the early days of JS and is usually hard to
deal with.

44
Source: giphy.com
45
https://wesbos.com/javascript/12-advanced-flow-control/69-refactoring-callback-hell-to-promise-land
46
Question

Have you ever dealt with a callback hell ? How did you manage to escape
from it?

47
Typical Use Case of Asynchronous Functions

Usually, when we are calling an asynchronous function, we really just want to


be notified later if the task has succeeded or failed.

In case of success, we want to process the result of the operation, and if the
operation errored, we want to know what went wrong.

48
A Nice Thing to Have

asynchronousFunction(...args)
.onSuccess(result => {
// Process the result...
})
.onFailure(err => {
// Take appropriate action...
})

49
And When We Need to Nest Callbacks

asynchronousFunction(args)
.onSuccess(fisrtResult => {
// Process `firstResult` and return data we will process further...
return secondResult
})
.onSuccess(secondResult => {
// Process `secondResult` and if needed, return something for next step...
return thirdResult
})
.onFailure(err => {
// If anything went wrong, the error will be handled here...
}) 50
The Promise of Better Async Code

I personally find that more readable and pleasant to code than nested
callbacks. And that what promises enable us to do.

51
Promises

52
Before Anything

Who thinks they're comfortable with promises? Vote in the quiz.

53
Definition

A Promise is an object representing the eventual completion or failure of an


asynchronous operation. Essentially, a promise is a returned object to which
you attach callbacks, instead of passing callbacks into a function.

54
Usage

Promises allow you to wrap the call of an asynchronous function in an object


and then tell that object what you want to do when the function ends.

It is similar to how we subscribe to event from DOM elements.

55
Promise Illustration

Back to our japa example with Campus France. If after six months our
application is accepted, we will be happy, and leave for greener pastures. That
corresponds to the success of our promise.

On the other hand, if we are not accepted, the promise is not fulfilled and we
go to a fallback action. Continue our internship, get a fulltime job, or maybe try
#Canada2024. 😄

56
How to Use Promises

● Get one from a function that returns a promise like fetch or create a new
one using the Promise constructor.
● Attach handlers to the Promise object via its then, catch, and finally
methods.

57
First example: Fetch API

fetch('https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/25.png')
.then(response => {
console.log(`Received response: ${response.status}`);
})
.catch(err => {
console.error('Something went wrong', err)
})

58
The Promise Constructor

As previously said, a promise is a wrapper around an asynchronous function


called the executor.

When the function ends, its notifies the promise about its status and the
promise asynchronously calls the callbacks you attached to it via its then and
catch methods.

59
The Notification Mechanism

To notify the promise about its termination, the executor function uses two
callbacks it received from the promise constructor.

● The resolve callback used to indicate success.


● The reject callback used to indicate failure.

60
The Promise Constructor

declare type PromiseConstructorLike = new <T>(


executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void
) => PromiseLike<T>

61
The “then” Method
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
then<TResult1 = T, TResult2 = never>(
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null
): Promise<TResult1 | TResult2>
62
The “catch” Method

/**
* Attaches a callback for only the rejection of the Promise.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of the callback.
*/
catch<TResult = never>(
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null
): Promise<T | TResult>;

63
The “finally” Method

/**
* Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The
* resolved value cannot be modified from the callback.
* @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).
* @returns A Promise for the completion of the callback.
*/
finally(onfinally?: (() => void) | undefined | null): Promise<T>

64
Example: Creating a Promise

readFile('/path/to/file', (err, data) => { const promise = new Promise((resolve, reject) => {
if (err) readFile('/path/to/file', (err, data) => {
console.log('Unable to read file', err) if (err)
else reject(err)
console.log('File content', data) else
}) resolve(data)
}) })
})

promise.then((data) => {
console.log('File content', data)
})

promise.catch(err => {
console.log('Unable to read file', err)
})
65
Chaining Promises
The then and catch methods return a Promise, allowing us to chain promises and
execute back to back asynchronous operations in a cleaner way than nested callbacks.

Source: stock.adobe.com 66
Back to Back Async Operations

doSomething(function (result) { doSomething()


doSomethingElse(result, function (newResult) { .then(function (result) {
doThirdThing(newResult, function (finalResult) { return doSomethingElse(result)
console.log(`Final result: ${finalResult}`) })
}, failureCallback) .then(function (newResult) {
}, failureCallback) return doThirdThing(newResult)
}, failureCallback) })
.then(function (finalResult) {
console.log(`Final result: ${finalResult}`)
})
.catch(failureCallback)

67
Sqlite Connection With Promises

function openDb() {
return new Promise((resolve, reject) => {
const db = new sqlite3.Database('/path/to/db.sqlite', (err) => {
if (err) reject(err)
})
db.on('open', () => resolve(db))
})
}

68
Getting Shonen Characters

db.on('open', () => { function getMangas(db, type) {


db.all("...", (err, mangas) => { return new Promise((resolve, reject) => {
db.all('...', (err, mangas) => {
if (err) {
if (err) reject(err)
console.log('Error fetching mangas', err)
resolve({ db, mangas })
return
})
}
})
mangas.forEach(manga => { }
const { id } = manga
db.each('', { id }, (err, c) => { function getCharacters(db, mangas) {
db.all('', { cid: c.id }, (err, skills) => { return new Promise((resolve, reject) => {
// Work with character and his skills db.all('', { ids: mangas.map(m => m.id) }, (e, arr) => {
}) if (e) reject(err)
}) resolve(characters)
})
})
})
})
}
})
69
Getting Shonen Characters II

openDb()
.then(db => getMangas(db, 'Shonen'))
.then(({ db, mangas }) => getCharacters(db, mangas))
.then(characters => {
characters.forEach(c => {
// ...
})
})
.catch(err => console.error(err))

70
Important Details

● then and catch always return a Promise.


● The calls to the Promise executor, and callbacks supplied to then and
catch are “wrapped” inside an implicit try-catch block. So, if an
exception is thrown, the underlined Promise will be rejected.
● If you intentionally throw an exception, the Promise will also be rejected.
● The executor is started before the Promise is returned.

71
Executor Start and Promise Constructor Return

const p = new Promise((resolve, reject) => {


// ...
})

// At this point, the executor has already started...

72
Third Quiz: Find This Program Output

What is the output of this code?

const promise = new Promise((resolve, reject) => {


console.log('1')
resolve()
}).then((result) => {
console.log('2')
})

console.log('3')
73
Vocabulary

● Pending Promise
● Resolved Promise
● Rejected Promise
● Settled Promise

74
Pending Promise

Source: stock.adobe.com 75
Resolved Promise

Source: www.standard.co.uk 76
Rejected Promise

77
Settled Promise

Source: istockphoto.com 78
Promise static Methods

● Promise.resolve
● Promise.reject
● Promise.all
● Promise.any
● Promise.race
● Promise.allSettled

79
Promise.all

Source: culturedvultures.com 80
Promise.any

Source: istockphoto.com 81
Promise.race

Source: https://statathlon.com
82
Async Functions

83
Benefits

Async functions allow us to write asynchronous code like we write


synchronous code.

They were introduced in the language with async and await keywords.

84
The async Keyword

async is a keyword attached to a function to indicate that the function can


contain asynchronous code and will always return a Promise. Other values are
wrapped in a resolved promise.

85
Async Function Example

async function f() {


return 42
}

f().then(value => console.log(value)) // Prints '42' in the console

86
Async Function Example II

async function f() {


return Promise.resolve(42)
}

f().then(value => console.log(value)) // Prints '42' in the console

87
The await Keyword

await is a keyword allowed only inside async functions and is used to


suspend the execution of the function until the promise it awaits is settled.

88
Promise Chain vs Async Functions: First Example

fetch('https://pokeapi.co/api/v2/pokemon/25') async function getPokemon() {


.then(response => { const url = 'https://pokeapi.co/api/v2/pokemon/25'
if (!response.ok) { try {
const status = response.status
const response = await fetch(url)
throw new Error(`Failed with a ${status} code`)
if (!response.ok) {
}
const status = response.status
return response.json()
throw new Error(`Request failed with a ${status} code`)
})
.then(data => console.log(data))
}
.catch(err => console.error(err)) const response = await response.json()
console.log(data)
} catch (error) {
console.error(error)
}
}

getPokemon()
89
Promise Chain vs Async Functions: Second Example

openDb() async function doStuff() {


.then(db => getMangas(db, 'Shonen')) try {
.then(({ db, mangas }) => { const db = await opendb()
return getCharacters(db, mangas) const mangas = await getMangas('Shonen')
}) const characters = await getCharacters(mangas)
.then(characters => { console.log(characters)
characters.forEach(c => { } catch (error) {
// ... console.error(err)
}) }
}) }
.catch(err => console.error(err))

90
Recap

91
Recap

● Choose Promises over callbacks when you can.


● Use async functions when you need to chain several promises
● Most browser and Node APIs offer Promises.
● Use promisify to wrap callback based functions inside promises.

92
References

93
References

● Introducing asynchronous JavaScript,


https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Introducing
● Using Promises,
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises
● https://javascript.info
● Asynchronous JS,
https://medium.com/gradeup/asynchronous-javascript-event-loop-1c8de41298dd

94
References II

● We have a problem with promises,


https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html
● Callback Hell, http://callbackhell.com
● What the heck is the event loop anyway?,
https://www.youtube.com/watch?v=8aGhZQkoFbQ
● L'asynchrone en JS sans le cringe, https://www.youtube.com/watch?v=GI6vm_NfqYg

95
96

You might also like