0% found this document useful (0 votes)
21 views29 pages

SALT Slides - Callbacks and Promises

Uploaded by

Jeff Deaf
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
21 views29 pages

SALT Slides - Callbacks and Promises

Uploaded by

Jeff Deaf
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 29

ASYNCHRONOUS

JAVASCRIPT
SYNCHRONOUS EXECUTION
ASYNCHRONOUS EXECUTION
Now we are getting more things done...
... in roughly the same amount of time!
BLOCKING CODE
const heavyCalcSync = (iterations) => {
while (iterations > 0) {
iterations--
}
return 'Heavy calc done!';
}

const doSyncExample = () => {


const el = document.getElementById('sync-ex-container');

el.innerHTML += 'Before<br>';
el.innerHTML += heavyCalcSync(9999999999);
el.innerHTML += '<br>After';
}
Run Clear

THIS CODE WILL FREEZE THE APP WHILE FETCHING DATA!


CALLBACKS
A callback function is a function that is passed as an argument to
another function.
The callback function is not being executed immediately. It's called
back at some later point in the function body.
A function that takes a callback as argument is a higher order function.
CALLBACK EXAMPLE
const somethingAsync = (ms, callback) => {
setTimeout(() => {
callback('Heavy calc done!');
}, ms);
}

const doCbExample = () => {


const el = document.getElementById('cb-ex-container');

el.innerHTML += 'Before\n';
somethingAsync(2000, (str) => {
el.innerHTML += str;
});
el.innerHTML += 'After\n';
}
Run Clear
So why shouldn't I just write all my functions to be asynchronous using
callbacks??
Complexity in writing and testing.
🔥CALLBACK HELL 🔥
Nested callbacks will quickly lead to unmaintainable code.
TYPICAL CODE USING CALLBACKS
const listOpenAccounts = (credentials, user, callback) => {
authorize(credentials, (err) => {
if (err) {
console.error('Authorization failure', err);
return callback(err);
}

dbClient.getAccountsFor(user, (err, accounts) => {


if (err) {
console.error('Error while fetching user accounts from database', err);
return callback(err);
}

const openAccounts = accounts.filter(account => account.isOpen());


return callback(null, openAccounts);
});
});
}
THIS IS BETTER
const listOpenAccounts = (credentials, user, callback) => {
authorize(credentials, (err) => {
if (err) {
console.log('Authorization failure', err);
return callback(err);
}

return fetchOpenAccounts(user, callback);


});
}

const fetchOpenAccounts = (user, callback) => {


dbClient.getAccountsFor(user, (err, accounts) => {
if (err) {
console.log('Error while fetching user accounts from database', err);
return callback(err);
}

return callback(null, accounts.filter(account => account.isOpen()))


});
}

Only ever call a callback once!


lint rule
AVOIDING CALLBACK HELL
A function should only do one thing
Create many small modules
Use Promise
PROMISE IN JAVASCRIPT

The Promise object represents the eventual


completion (or failure) of an asynchronous
operation, and its resulting value. — MDN
POSSIBLE STATES OF A PROMISE
Pending
Fulfilled
Rejected
FULFILLED
const allIntegers = nums => nums.every(n => typeof n === 'number' && n % 1 === 0);

const isEvensAsync = (numbers) => {


return new Promise((resolve, reject) => {
if (allIntegers(numbers)) {
resolve(numbers.every(n => n % 2 === 0));
} else {
reject(new Error('Illegal argument: Not an integer.'));
}
});
}

const result = isEvensAsync([2,4,6]);


console.log(result); // Promise { true }, the promise is fulfilled.
PENDING
const isEvensAsync = (ns) => {
const allIntegers = ns => ns.every(n => Number.isInteger(n));

return new Promise((resolve, reject) => {


setTimeout(() => { // Delay execution
if (allIntegers(ns)) {
resolve(ns.every(n => n % 2 === 0));
} else {
reject(new Error('Illegal argument: Not an integer.'));
}
}, 100);
});
}

const result = isEvensAsync([2,4,6]);


console.log(result); // Promise { pending }, the promise is pending.

result.then(r => console.log(r)); // true


REJECTED
const isEvensAsync = (ns) => {
const allIntegers = ns => ns.every(n => Number.isInteger(n));

return new Promise((resolve, reject) => {


setTimeout(() => { // Delay execution
if (allIntegers(ns)) {
resolve(ns.every(n => n % 2 === 0));
} else {
reject(new Error('Illegal argument: Not an integer.'));
}
}, 100);
});
}

const result = isEvensAsync([2,4,'foo']);


console.log(result); // Promise { pending }, the promise is pending.

result
.then(r => console.log(r)) // the promise is rejected
.catch(err => console.log('error: ', err)); // error: ...
PROMISES ARE CHAINABLE
Promise.prototype.then returns a new promise!
const txt = new Promise(resolve => resolve('foo'));

txt
.then(t => t + ' bar')
.then(t => t + ' baz')
.then(t => console.log(t)) // 'foo bar baz'
.catch(err => console.log(err));
AVOIDING CALLBACK HELL
Callbacks
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('Got the final result: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);

Promises
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => console.log('Got the final result: ' + finalResult))
.catch(failureCallback);
PROMISE.ALL
const weatherPromise = new Promise(
resolve =>resolve({ monday: '19 deg', tuesday: '21 deg' })
);
const trafficPromise = new Promise(
resolve => resolve({ lindhagenvägen: 'mild traffic, no queues' })
);
const openingHoursPromise = new Promise(
resolve => resolve({ eatery: 'open: 10.00 - 20.00' })
);

const allPromises = Promise.all(


[weatherPromise, trafficPromise, openingHoursPromise]
);

// runs when all are fulfilled


allPromises.then(result => {
console.log(result) // [{ monday: '19 deg', tuesday: '21 deg' },
// { lindhagenvägen: 'mild traffic, no queues' },
// { eatery: 'open: 10.00 - 20.00' } ]

decideWhereToGo(result); // some function that requires ALL data


});
HANDLING ERRORS
One error stops the entire chain
const p1 = new Promise(resolve => resolve('foo'));
const p2 = new Promise((resolve, reject) => reject(new Error('haw haw')));

const total = Promise.all([p1, p2]); // continue when all are done

total
.then(r => console.log(r))
.catch(err => console.log('error:', err.message)); // haw haw

Catch all exceptions before calling .all


const p1 = new Promise(resolve => resolve('foo'));
const p2 = new Promise((resolve, reject) => reject(new Error('haw haw')));

const total = Promise.all([


p1.catch(err => console.error(err.message)),
p2.catch(err => console.error(err.message))
]);

total.then(r => console.log(r)); // [ 'foo', 'haw haw' ]


ERRORS IN CALLBACKS...
THE SPECIAL CASE
const listOpenAccounts = (credentials, user, callback) => {
authorize(credentials, (err) => {
if (err) { // <= checking for errors
console.log('Authorization failure', err);
return callback(err); // <= passing as first parameter
}

dbClient.getAccountsFor(user, (err, accounts) => {


if (err) { // <= checking for errors
console.log('Error while fetching user accounts from database', err);
return callback(err); // <-- Error can happen here too!
1 }

const openAccounts = accounts.filter(account => account.isOpen());


return callback(null, openAccounts); // <= OK, phew - err is undefined
});
});
}
TESTING FOR CALLBACK ERRORS
// api.js
const getOne = (id, callback) => {
// loads of code here
// ...
// when failing
callback(new Error('FAAAAAIL'));
}

// tests.js
const assert = require('assert');
const api = require('./api.js');

describe('my function should fail for non-existing ids', () => {


it('should err on non-existing id', (done) => {
api.getOne('fail this', (err) => {
assert.strictNotEqual(err, undefined); // that the error is present (not und
assert.strictEqual(err.message, 'FAAAAAIL'); // verify the correct message
done();
});
});
});
TESTING FOR PROMISE ERRORS
// api.js
const imOk = () => new Promise(resolve => resolve('foo'));
const imNotOk = () => new Promise((resolve, reject) => reject(new Error('haw haw')));

// tests.js
const assert = require('assert');
const api = require('./api.js');

describe('promises', () => {
it('should validate that promise is resolved', (done) => {
api.imOk()
.then((val) => {
assert.strictEqual('foo', val);
done();
}).catch((err) => { // <-- add catch to avoid timeout for rejects
assert.fail('ImOk should not end up in catch');
});
});

it('should detect rejected promises', (done) => {


api.imNotOk()
.then(() => {
assert.fail('imNotOk should not resolve'); // < -- will end test execution
}).catch((err) => {
assert.strictEqual('haw haw', err.message);
done(); // <-- call done(); or see it time out :)
});
});
});
THE PROBLEM WITH PROMISES
Nested code
const fetchFoo = () => new Promise((resolve) => {
setTimeout(() => resolve('foo'), 500);
});

const fetchBar = (foo) => new Promise(resolve => {


setTimeout(() => resolve(foo + ' bar'), 200);
});

const asyncFooBarBaz = () => new Promise((resolve) => {


fetchFoo().then(result => {
fetchBar(result)
.then(result => resolve(result + ' baz'));
});
});

asyncFooBarBaz().then(val => console.log(val)); // foo bar baz

This looks an awful lot like the callback hell we just left!
PRO TIPS ON CALLBACKS
https://appliedtechnology.github.io/protips/passingFunctions
https://appliedtechnology.github.io/protips/callingBack
https://appliedtechnology.github.io/protips/writingDeepFunctions
https://appliedtechnology.github.io/protips/makingPromises

You might also like