L’enfer des callbacks

44
L’enfer des callbacks Un cas d’étude en *(Node)JS Aurélien Bourdon @aurelienbourdon

Transcript of L’enfer des callbacks

Page 1: L’enfer des callbacks

L’enfer des callbacksUn cas d’étude en *(Node)JS

Aurélien Bourdon@aurelienbourdon

Page 2: L’enfer des callbacks

describe('.totalValue', function() {

it('should calculate the total value of items in a space', function(done) {

var table = new Item('table', 'dining room', '07/23/2014', '1', '3000');

var chair = new Item('chair', 'living room', '07/23/2014', '3', '300');

var couch = new Item('couch', 'living room', '07/23/2014', '2', '1100');

var chair2 = new Item('chair', 'living room', '07/23/2014', '4', '500');

var bed = new Item('bed', 'dining room', '07/23/2014', '1', '2000');

table.save(function() {

chair.save(function() {

couch.save(function() {

chair2.save(function() {

bed.save(function() {

Item.totalValue({room: 'dining room'}, function(totalValue) {

expect(totalValue).to.equal(5000); done();

});

});

});

});

});

});

});

});Sources

http://thecodebarbarian.com/2015/03/20/callback-hell-is-a-mythhttps://atdreamstate.wordpress.com/2014/12/26/hell-hellfire/

Page 3: L’enfer des callbacks

Pourquoi est-ce l’enfer ?

Page 4: L’enfer des callbacks

Pourquoi est-ce l’enfer ?

Lecture en cascade

Page 5: L’enfer des callbacks

Pourquoi est-ce l’enfer ?

Lecture en cascade

Absence de description fonctionnelle

Page 6: L’enfer des callbacks

Pourquoi est-ce l’enfer ?

Lecture en cascade

Couplage fort entre les fonctions

Absence de description fonctionnelle

Page 7: L’enfer des callbacks

Pourquoi est-ce l’enfer ?

Lecture en cascade

Lisibilité hardue

Maintenance difficile

Sourcehttp://memesvault.com/sad-meme-face/

Absence de description fonctionnelle

Couplage fort entre les fonctions

Page 8: L’enfer des callbacks

Cas d’étude

Page 9: L’enfer des callbacks

function parseCurrentDir() {

fs.readdir('.', function (err, files) {

if (err) {

console.error(err);

} else {

files.forEach(function (file) {

fs.readFile(file, 'utf8', function (err, content) {

if (err) {

console.error(err);

} else {

console.log(content);

}

});

});

}

});

}

Page 10: L’enfer des callbacks

#1 Modularisation

Page 11: L’enfer des callbacks

Modularisation ?

Nommage et externalisation des fonctions

Page 12: L’enfer des callbacks

function parseCurrentDir() {

fs.readdir('.', function (err, files) {

if (err) {

console.error(err);

} else {

files.forEach(function (file) {

fs.readFile(file, 'utf8', function (err, content) {

if (err) {

console.error(err);

} else {

console.log(content);

}

});

});

}

});

}

Page 13: L’enfer des callbacks

function parseCurrentDir() {

fs.readdir('.', function readFiles(err, files) {

if (err) {

console.error(err);

} else {

files.forEach(function readFile(file) {

fs.readFile(file, 'utf8', function printContent(err, content) {

if (err) {

console.error(err);

} else {

console.log(content);

}

});

});

}

});

}

Page 14: L’enfer des callbacks

function readFile(file) {

fs.readFile(file, 'utf8', printContent);

}

function printContent(err, content) {

if (err) {

console.error(err);

} else {

console.log(content);

}

}

function parseCurrentDir() {

fs.readdir('.', readFiles);

}

function readFiles(err, files) {

if (err) {

console.error(err);

} else {

files.forEach(readFile);

}

}

Page 15: L’enfer des callbacks

function readFile(file) {

fs.readFile(file, 'utf8', printContent);

}

function printContent(err, content) {

if (err) {

console.error(err);

} else {

console.log(content);

}

}

function parseCurrentDir() {

fs.readdir('.', readFiles);

}

function readFiles(err, files) {

if (err) {

console.error(err);

} else {

files.forEach(readFile);

}

}

Page 16: L’enfer des callbacks

#2 Découplage

Page 17: L’enfer des callbacks

Découplage ?

Séparation des traitements du flow d’exécution

Page 18: L’enfer des callbacks

Découplage ?

Séparation des traitements du flow d’exécution

En utilisant des promises :-)

Page 19: L’enfer des callbacks

Promises ?

“Abstraction servant de proxy pour un résultat non-connu au moment où il est référencé pour la

première fois, car son calcul ou son obtention se feront “plus tard” à l’exécution”

- Wikipédia

Page 20: L’enfer des callbacks

Promises !

(...)

Page 21: L’enfer des callbacks

(...)

Promises !

https://github.com/kriskowal/q

Page 22: L’enfer des callbacks

function parseCurrentDir() {

fs.readdir('.', readFiles);

}

Page 23: L’enfer des callbacks

function parseCurrentDir() {

fs.readdir('.', function (err, files) {

});

}

Page 24: L’enfer des callbacks

function parseCurrentDir() {

fs.readdir('.', function (err, files) {

});

}

Page 25: L’enfer des callbacks

function parseCurrentDir() {

Q.Promise(function (resolve, reject, notify) {

fs.readdir('.', function (err, files) {

});

});

}

Page 26: L’enfer des callbacks

function parseCurrentDir() {

Q.Promise(function (resolve, reject, notify) {

fs.readdir('.', function (err, files) {

if (err) {

reject(err);

} else {

resolve(files);

}

});

});

}

Page 27: L’enfer des callbacks

function parseCurrentDir() {

return Q.Promise(function (resolve, reject, notify) {

fs.readdir('.', function (err, files) {

if (err) {

reject(err);

} else {

resolve(files);

}

});

});

}

Page 28: L’enfer des callbacks

function parseCurrentDir() {

return Q.Promise(function (resolve, reject, notify) {

fs.readdir('.', function (err, files) {

if (err) {

reject(err);

} else {

resolve(files);

}

});

});

}

Séparation des traitements du flow d’exécution

readFiles(files);

Page 29: L’enfer des callbacks

function listCurrentDirFiles() {

return Q.Promise(function (resolve, reject, notify) {

fs.readdir('.', function (err, files) {

if (err) {

reject(err);

} else {

resolve(files);

}

});

});

}

Séparation des traitements du flow d’exécution

readFiles(files);

Page 30: L’enfer des callbacks

“Mais comment lier notre fonction avec la suivante ?”

Page 31: L’enfer des callbacks

“Mais comment lier notre fonction avec la suivante ?”

En utilisant le chaînage de promises !

Page 32: L’enfer des callbacks

listCurrentDirFiles()

Page 33: L’enfer des callbacks

listCurrentDirFiles()

.then(readFiles)

Page 34: L’enfer des callbacks

listCurrentDirFiles()

.then(readFiles)

.then(...)

Page 35: L’enfer des callbacks

listCurrentDirFiles()

.then(readFiles)

.then(...)

.catch(console.error)

Page 36: L’enfer des callbacks

listCurrentDirFiles()

.then(readFiles)

.then(...)

.catch(console.error)

.done()

Page 37: L’enfer des callbacks

“Du coup au final ça donne quoi ?”

Page 38: L’enfer des callbacks

function parseCurrentDir() {

fs.readdir('.', function (err, files) {

if (err) {

console.error(err);

} else {

files.forEach(function (file) {

fs.readFile(file, 'utf8', function (err, content) {

if (err) {

console.error(err);

} else {

console.log(content);

}

});

});

}

});

}

Page 39: L’enfer des callbacks

function parseCurrentDir() {

listCurrentDirFiles()

.then(readFiles)

.then(printContents)

.catch(console.error)

.done();

}

Page 40: L’enfer des callbacks

function readFile(file) {

return Q.Promise(function (resolve, reject) {

fs.readFile(file, 'utf8', function (err, content) {

if (err) {

reject(err);

} else {

resolve(content);

}

});

});

}

function printContents(contents) {

contents.forEach(function (content) {

if (content.state === 'fulfilled') {

console.log(content.value);

} else {

console.log('Unable to parse file due to %s', content.reason);

}

});

}

function listCurrentDirFiles() {

return Q.Promise(function (resolve, reject) {

fs.readdir('.', function (err, files) {

if (err) {

reject(err);

} else {

resolve(files);

}

});

});

}

function readFiles(files) {

return Q.allSettled(files.map(readFile));

}

function readFile(file) {

return Q.Promise(function (resolve, reject) {

fs.readFile(file, 'utf8', function (err, content) {

if (err) {

reject(err);

} else {

resolve(content);

}

});

});

}

function printContents(contents) {

contents.forEach(function (content) {

if (content.state === 'fulfilled') {

console.log(content.value);

} else {

console.error(content.reason);

}

});

}

Page 41: L’enfer des callbacks

function readFile(file) {

return Q.Promise(function (resolve, reject) {

fs.readFile(file, 'utf8', function (err, content) {

if (err) {

reject(err);

} else {

resolve(content);

}

});

});

}

function printContents(contents) {

contents.forEach(function (content) {

if (content.state === 'fulfilled') {

console.log(content.value);

} else {

console.log('Unable to parse file due to %s', content.reason);

}

});

}

function listCurrentDirFiles() {

return Q.denodeify(fs.readdir)('.');

}

function readFiles(files) {

return Q.allSettled(files.map(readFile));

}

function readFile(file) {

return Q.denodeify(fs.readFile)(file, 'utf8');

}

function printContents(contents) {

contents.forEach(function (content) {

if (content.state === 'fulfilled') {

console.log(content.value);

} else {

console.error(content.reason);

}

});

}

Page 42: L’enfer des callbacks

Pour aller plus loin

D’autres approches peuvent être utilisées e.g., Async.JS (https://github.com/caolan/async)

Page 43: L’enfer des callbacks

Référenceshttp://callbackhell.com/

http://stackabuse.com/avoiding-callback-hell-in-node-js/

http://thecodebarbarian.com/2015/03/20/callback-hell-is-a-myth

https://github.com/kriskowal/q

https://strongloop.com/strongblog/node-js-callback-hell-promises-generators/

http://colintoh.com/blog/staying-sane-with-asynchronous-programming-promises-and-generators

http://blog.ippon.fr/2015/01/27/les-promesses-en-angularjs-33/

http://complexitymaze.com/2014/03/03/javascript-promises-a-comparison-of-libraries/

https://strongloop.com/strongblog/promises-in-node-js-with-q-an-alternative-to-callbacks/

Page 44: L’enfer des callbacks

L’enfer des callbacksUn cas d’étude en *(Node)JS

Aurélien Bourdon@aurelienbourdon