Promise ✋ 🙂 🤚 async programming hero
About me 👤 Wiktor Toporek 💼 PHP → Frontend → Node.js 👍 Functional programming ❤ Elm 🐦 Twitter: @vViktorPL
„ 11 years ago in May I did 10 days of hard work,
„ 11 years ago in May I did 10 days of hard work, I didn’t sleep much.”
Brendan Eich „ 11 years ago in May I did 10 days of hard work, I didn’t sleep much.”
JavaScript
JavaScript GUI
JavaScript GUI while (!animationEnd) { animateStep(); sleep(10); }
JavaScript GUI while (!animationEnd) { animateStep(); sleep(10); } 😕
JavaScript GUI ASYNC while (!animationEnd) { animateStep(); sleep(10); } 😕
JavaScript GUI ASYNC while (!animationEnd) { animateStep(); sleep(10); } 😕 setInterval(function(){}, 10);
JavaScript GUI ASYNC while (!animationEnd) { animateStep(); sleep(10); } 😕 setInterval(function(){}, 10); requestAnimationFrame(step);
JavaScript GUI ASYNC while (!animationEnd) { animateStep(); sleep(10); } 😕 setInterval(function(){}, 10); requestAnimationFrame(step); 👍
JavaScript GUI ASYNC while (!animationEnd) { animateStep(); sleep(10); } 😕 setInterval(function(){}, 10); requestAnimationFrame(step); 👍
JavaScript GUI ASYNC CALLBACKS while (!animationEnd) { animateStep(); sleep(10); } 😕 setInterval(function(){}, 10); requestAnimationFrame(step); 👍
Task #1
Task #1 Hello World! 🕑 1s… 🕑 1s…
function delayedHelloWorld() { setTimeout(() => { console.log('Hello'); }, 1000); }
function delayedHelloWorld() { setTimeout(() => { console.log('Hello'); setTimeout(() => { console.log('World!') }, 1000); }, 1000); }
function delayedHelloWorld() { setTimeout(() => { console.log('Hello'); setTimeout(() => { console.log('World!') }, 1000); }, 1000); }
Problem #1 • Callback as not last function argument
Problem #1 • Callback as not last function argument foo( function() { someOtherFunction( function() { someFunctionWithCb( function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } }, 1, 'someString', ) }, 'a' ); }, 1234 );
Problem #1 • Callback as not last function argument foo( function() { someOtherFunction( function() { someFunctionWithCb( function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } }, 1, 'someString', ) }, 'a' ); }, 1234 ); vs
Problem #1 • Callback as not last function argument foo( function() { someOtherFunction( function() { someFunctionWithCb( function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } }, 1, 'someString', ) }, 'a' ); }, 1234 ); foo( 1234, function() { someOtherFunction( 'a', function() { someFunctionWithCb( 1, 'someString', function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } } ) } ); } ); vs
Problem #1 • Callback as not last function argument foo( function() { someOtherFunction( function() { someFunctionWithCb( function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } }, 1, 'someString', ) }, 'a' ); }, 1234 ); foo( 1234, function() { someOtherFunction( 'a', function() { someFunctionWithCb( 1, 'someString', function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } } ) } ); } ); vs
Problem #1 • Callback as not last function argument foo( function() { someOtherFunction( function() { someFunctionWithCb( function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } }, 1, 'someString', ) }, 'a' ); }, 1234 ); foo( 1234, function() { someOtherFunction( 'a', function() { someFunctionWithCb( 1, 'someString', function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } } ) } ); } ); vs
Problem #1 • Callback as not last function argument foo( function() { someOtherFunction( function() { someFunctionWithCb( function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } }, 1, 'someString', ) }, 'a' ); }, 1234 ); foo( 1234, function() { someOtherFunction( 'a', function() { someFunctionWithCb( 1, 'someString', function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } } ) } ); } ); vs
Problem #1 • Callback as not last function argument foo( function() { someOtherFunction( function() { someFunctionWithCb( function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } }, 1, 'someString', ) }, 'a' ); }, 1234 ); foo( 1234, function() { someOtherFunction( 'a', function() { someFunctionWithCb( 1, 'someString', function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } } ) } ); } ); vs
Problem #1 • Callback as not last function argument foo( function() { someOtherFunction( function() { someFunctionWithCb( function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } }, 1, 'someString', ) }, 'a' ); }, 1234 ); foo( 1234, function() { someOtherFunction( 'a', function() { someFunctionWithCb( 1, 'someString', function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } } ) } ); } ); vs
Problem #1 • Callback as not last function argument foo( function() { someOtherFunction( function() { someFunctionWithCb( function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } }, 1, 'someString', ) }, 'a' ); }, 1234 ); foo( 1234, function() { someOtherFunction( 'a', function() { someFunctionWithCb( 1, 'someString', function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } } ) } ); } ); vs
Promise 101 🎓
let promise1 = new Promise(function (resolve, reject) { // ... });
let promise1 = new Promise(function (resolve, reject) { // ... }); call on success
let promise1 = new Promise(function (resolve, reject) { // ... }); call on success call on error
let promise1 = new Promise(function (resolve, reject) { // ... }); call on success call on error 🕒 Pending
let promise1 = new Promise(function (resolve, reject) { // ... }); call on success call on error 🕒 Pending ✅ Fulfilled
let promise1 = new Promise(function (resolve, reject) { // ... }); call on success call on error 🕒 Pending ✅ Fulfilled ❌ Rejected
let promise1 = new Promise(function (resolve, reject) { // ... }); call on success call on error 🕒 Pending ✅ Fulfilled ❌ Rejected Settled }
let promise1 = new Promise(function (resolve, reject) { // ... }); call on success call on error promise1.then(() => /* something to do on success */) .catch(() => /* something to do on error */) 🕒 Pending ✅ Fulfilled ❌ Rejected Settled }
function delay(ms) { return new Promise(resolve => { setTimeout(resolve, ms); }); }
function delay(ms) { return new Promise(resolve => { setTimeout(resolve, ms); }); } const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
function delayedHelloWorld() { delay(1000).then(() => { console.log('Hello'); delay(1000).then( () => { console.log('World!'); } ) }) }
Problem #2
Źródło: https://medium.com/gousto-engineering-techbrunch/avoiding-callback-hell-97734e303d
Źródło: https://medium.com/gousto-engineering-techbrunch/avoiding-callback-hell-97734e303d
function delayedHelloWorld() { delay(1000) .then(() => { console.log('Hello'); return delay(1000); }) .then(() => { console.log('World!'); }) }
function delayedHelloWorld() { delay(1000) .then(() => { console.log('Hello'); return delay(1000); }) .then(() => { console.log('World!'); }) }
function delayedHelloWorld() { delay(1000) .then(() => { console.log('Hello'); return delay(1000); }) .then(() => { console.log('World!'); }) } sleep(1000); console.log('Hello'); sleep(1000); console.log('World!');
function delayedHelloWorld() { delay(1000) .then(() => { console.log('Hello'); return delay(1000); }) .then(() => { console.log('World!'); }) } sleep(1000); console.log('Hello'); sleep(1000); console.log('World!'); sleep(1000); console.log('Hello'); sleep(1000); console.log('World!');
function delayedHelloWorld() { delay(1000) .then(() => { console.log('Hello'); }) .then(() => delay(1000)) .then(() => { console.log('World!'); }) }
Problem #3
Problem #3 delayedHelloWorld(); console.log('after hello world');
Problem #3 delayedHelloWorld(); console.log('after hello world');
function delayedHelloWorld() { return delay(1000) .then(() => { console.log('Hello'); }) .then(() => delay(1000)) .then(() => { console.log('World!'); }) }
function delayedHelloWorld() { return delay(1000) .then(() => { console.log('Hello'); }) .then(() => delay(1000)) .then(() => { console.log('World!'); }) } delayedHelloWorld().then(() => { console.log('after hello world'); });
function delayedHelloWorld() { return delay(1000) .then(() => { console.log('Hello'); }) .then(() => delay(1000)) .then(() => { console.log('World!'); }) } delayedHelloWorld().then(() => { console.log('after hello world'); });
Task #2 # File name 1 note.txt ❌ 2 pancake.jpg ❌ 3 book.pdf ❌ 4 schema.png ❌
Task #2 # File name 1 note.txt ❌ 2 pancake.jpg ❌ 3 book.pdf ❌ 4 schema.png ❌ ✔ ✔ ✔
Task #2 # File name 1 note.txt ❌ 2 pancake.jpg ❌ 3 book.pdf ❌ 4 schema.png ❌ ✔ ✔ ✔ Remove selected
Task #2
Task #2
Task #2 Column name id name filesystemPath File table
Task #2 Column name id name filesystemPath File table class FileRepository { getByIds(ids) { // ... } delete(ids) { // ... } }
Task #2 Column name id name filesystemPath File table class FileRepository { getByIds(ids) { // ... } delete(ids) { // ... } }
Task #2 Column name id name filesystemPath File table class FileRepository { getByIds(ids) { // ... } delete(ids) { // ... } }
function removeFiles(ids) { return filesRepository.getByIds(ids).then( fileEntities => { fileEntities.forEach( fileEntity => removeFileInFS(fileEntity.filesystemPath) ); filesRepository.delete(ids); } ); }
function removeFiles(ids) { return filesRepository.getByIds(ids).then( fileEntities => { fileEntities.forEach( fileEntity => removeFileInFS(fileEntity.filesystemPath) ); filesRepository.delete(ids); } ); } function removeFileInFS(filePath) { return new Promise( (resolve, reject) => { fs.unlink(filePath, (err) => { if (err) { reject(err); return; } resolve(); }); } ) }
function removeFiles(ids) { return filesRepository.getByIds(ids).then( fileEntities => { fileEntities.forEach( fileEntity => removeFileInFS(fileEntity.filesystemPath) ); filesRepository.delete(ids); } ); } function removeFileInFS(filePath) { return new Promise( (resolve, reject) => { fs.unlink(filePath, (err) => { if (err) { reject(err); return; } resolve(); }); } ) } const removeFileInFS = util.promisify(fs.unlink);
function removeFiles(ids) { return filesRepository.getByIds(ids).then( fileEntities => { fileEntities.forEach( fileEntity => removeFileInFS(fileEntity.filesystemPath) ); filesRepository.delete(ids); } ); } function removeFileInFS(filePath) { return new Promise( (resolve, reject) => { fs.unlink(filePath, (err) => { if (err) { reject(err); return; } resolve(); }); } ) } const removeFileInFS = util.promisify(fs.unlink); require('fs').promises
function removeFiles(ids) { return filesRepository.getByIds(ids).then( fileEntities => { fileEntities.forEach( fileEntity => removeFileInFS(fileEntity.filesystemPath) ); filesRepository.delete(ids); } ); }
function removeFiles(ids) { return filesRepository.getByIds(ids).then( fileEntities => { fileEntities.forEach( fileEntity => removeFileInFS(fileEntity.filesystemPath) ); filesRepository.delete(ids); } ); } Problem: resolved before actual deletion occurs
function removeFiles(ids) {
function removeFiles(ids) { return new Promise(
function removeFiles(ids) { return new Promise( resolve => {
function removeFiles(ids) { return new Promise( resolve => {
function removeFiles(ids) { return new Promise( resolve => { filesRepository.getByIds(ids).then(
function removeFiles(ids) { return new Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) {
function removeFiles(ids) { return new Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop();
function removeFiles(ids) { return new Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) {
function removeFiles(ids) { return new Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) { filesRepository.delete(ids).then(resolve)
function removeFiles(ids) { return new Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) { filesRepository.delete(ids).then(resolve) return;
function removeFiles(ids) { return new Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) { filesRepository.delete(ids).then(resolve) return; }
function removeFiles(ids) { return new Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) { filesRepository.delete(ids).then(resolve) return; }
function removeFiles(ids) { return new Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) { filesRepository.delete(ids).then(resolve) return; } return removeFileInFS(currentFile).then(
function removeFiles(ids) { return new Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) { filesRepository.delete(ids).then(resolve) return; } return removeFileInFS(currentFile).then( () => removeFilesInFS(fileEntities)
function removeFiles(ids) { return new Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) { filesRepository.delete(ids).then(resolve) return; } return removeFileInFS(currentFile).then( () => removeFilesInFS(fileEntities) );
function removeFiles(ids) { return new Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) { filesRepository.delete(ids).then(resolve) return; } return removeFileInFS(currentFile).then( () => removeFilesInFS(fileEntities) ); }
function removeFiles(ids) { return new Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) { filesRepository.delete(ids).then(resolve) return; } return removeFileInFS(currentFile).then( () => removeFilesInFS(fileEntities) ); } );
function removeFiles(ids) { return new Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) { filesRepository.delete(ids).then(resolve) return; } return removeFileInFS(currentFile).then( () => removeFilesInFS(fileEntities) ); } ); }
function removeFiles(ids) { return new Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) { filesRepository.delete(ids).then(resolve) return; } return removeFileInFS(currentFile).then( () => removeFilesInFS(fileEntities) ); } ); } );
function removeFiles(ids) { return new Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) { filesRepository.delete(ids).then(resolve) return; } return removeFileInFS(currentFile).then( () => removeFilesInFS(fileEntities) ); } ); } ); }
function removeFiles(ids) { return new Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) { filesRepository.delete(ids).then(resolve) return; } return removeFileInFS(currentFile).then( () => removeFilesInFS(fileEntities) ); } ); } ); } Problem: bad readability
async function removeFiles(ids) { const fileEntities = await filesRepository.getByIds(ids); for (const fileEntity of fileEntities) { await removeFileInFS(fileEntity.filesystemPath); } await filesRepository.delete(ids); }
async function removeFiles(ids) { const fileEntities = await filesRepository.getByIds(ids); for (const fileEntity of fileEntities) { await removeFileInFS(fileEntity.filesystemPath); } await filesRepository.delete(ids); } Problem: sequenced
function removeFiles(ids) { let numberOfFilesToDelete = ids.length; return new Promise( resolve => { filesRepository.getByIds(ids).then( fileEntities => { fileEntities.forEach(fileEntity => { removeFileInFS(fileEntity.filesystemPath).then( () => { numberOfFilesToDelete--; if (numberOfFilesToDelete === 0) { filesRepository.delete(ids).then(resolve); } } ) }); } ); } ); }
function removeFiles(ids) { let numberOfFilesToDelete = ids.length; return new Promise( resolve => { filesRepository.getByIds(ids).then( fileEntities => { fileEntities.forEach(fileEntity => { removeFileInFS(fileEntity.filesystemPath).then( () => { numberOfFilesToDelete--; if (numberOfFilesToDelete === 0) { filesRepository.delete(ids).then(resolve); } } ) }); } ); } ); } Problem: bad readability
Promise.all
Promise.all
function removeFiles(ids) { return filesRepository.getByIds(ids) .then( fileEntities => { const removedFiles = fileEntities.map( fileEntity => removeFileInFS(fileEntity.filesystemPath) ); return Promise.all(removedFiles); } ) .then(() => filesRepository.delete(ids)) }
async function removeFiles(ids) { const fileEntities = await filesRepository.getByIds(ids) await Promise.all( fileEntities.map(fileEntity => removeFile(fileEntity.filesystemPath)) ); await filesRepository.delete(ids); }
async function removeFiles(ids) { const fileEntities = await filesRepository.getByIds(ids) await Promise.all( fileEntities.map(fileEntity => removeFile(fileEntity.filesystemPath)) ); await filesRepository.delete(ids); } Problem: what if some file removal fails?
Promise combinators
Promise.allSettled
Promise.allSettled { status: 'fulfilled', value }
Promise.allSettled { status: 'fulfilled', value } { status: 'rejected', reason }
async function removeFiles(ids) { const fileEntities = await filesRepository.getByIds(ids); const removedFilesStatus = await Promise.allSettled( fileEntities.map(fileEntity => removeFile(fileEntity.filesystemPath).then( () => fileEntity.id ) ) ); const removedFiles = removedFilesStatus .filter(({ status }) => status === 'fulfilled') .map(({ value }) => value); await filesRepository.delete( ids.filter(id => removedFiles.includes(id)) ); if (removedFiles.length !== fileEntities.length) { throw Error(`Couldn't remove some files.`); } }
Promise.race
Promise.race const someRequestWithTimeout = (url, timeoutMs) => Promise.race([ fetch(url), delay(timeoutMs).then(() => Promise.reject('TIMEOUT')) ]);
Tips
Tips 📜Keep code "flat"
Tips 📜Keep code "flat" ⛓Wait for side-effects, benefit from promise chaining
Tips 📜Keep code "flat" ⛓Wait for side-effects, benefit from promise chaining ☣Do not mutate state outside "then"
Tips 📜Keep code "flat" ⛓Wait for side-effects, benefit from promise chaining ☣Do not mutate state outside "then" 3Use Promise combinators for multiple promises
Tips 📜Keep code "flat" ⛓Wait for side-effects, benefit from promise chaining ☣Do not mutate state outside "then" 3Use Promise combinators for multiple promises 👓async/await can be more readable
Drawback
Drawback Promise.then
Drawback Promise.then == map + flatMap
Drawback Promise.then == map + flatMap 😎 Pragmatic
Drawback Promise.then == map + flatMap 😎 Pragmatic 🧐 Category theory compatible
7 Single value 8 Multiple values ⚡ Sync
7 Single value 8 Multiple values ⚡ Sync Value
7 Single value 8 Multiple values ⚡ Sync Value Array
7 Single value 8 Multiple values ⚡ Sync Value Array 🕑 Async
7 Single value 8 Multiple values ⚡ Sync Value Array 🕑 Async Promise
7 Single value 8 Multiple values ⚡ Sync Value Array 🕑 Async Promise Stream / Observable
Thank you 🙇

Promise: async programming hero

  • 1.
  • 2.
    About me 👤 WiktorToporek 💼 PHP → Frontend → Node.js 👍 Functional programming ❤ Elm 🐦 Twitter: @vViktorPL
  • 5.
    „ 11 yearsago in May I did 10 days of hard work,
  • 6.
    „ 11 yearsago in May I did 10 days of hard work, I didn’t sleep much.”
  • 7.
    Brendan Eich „ 11years ago in May I did 10 days of hard work, I didn’t sleep much.”
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
    JavaScript GUI ASYNC while (!animationEnd) { animateStep(); sleep(10); }😕 setInterval(function(){}, 10); requestAnimationFrame(step);
  • 20.
    JavaScript GUI ASYNC while (!animationEnd) { animateStep(); sleep(10); }😕 setInterval(function(){}, 10); requestAnimationFrame(step); 👍
  • 21.
    JavaScript GUI ASYNC while (!animationEnd) { animateStep(); sleep(10); }😕 setInterval(function(){}, 10); requestAnimationFrame(step); 👍
  • 22.
    JavaScript GUI ASYNC CALLBACKS while (!animationEnd) { animateStep(); sleep(10); }😕 setInterval(function(){}, 10); requestAnimationFrame(step); 👍
  • 23.
  • 24.
  • 25.
    function delayedHelloWorld() { setTimeout(()=> { console.log('Hello'); }, 1000); }
  • 26.
    function delayedHelloWorld() { setTimeout(()=> { console.log('Hello'); setTimeout(() => { console.log('World!') }, 1000); }, 1000); }
  • 27.
    function delayedHelloWorld() { setTimeout(()=> { console.log('Hello'); setTimeout(() => { console.log('World!') }, 1000); }, 1000); }
  • 28.
    Problem #1 • Callbackas not last function argument
  • 29.
    Problem #1 • Callbackas not last function argument foo( function() { someOtherFunction( function() { someFunctionWithCb( function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } }, 1, 'someString', ) }, 'a' ); }, 1234 );
  • 30.
    Problem #1 • Callbackas not last function argument foo( function() { someOtherFunction( function() { someFunctionWithCb( function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } }, 1, 'someString', ) }, 'a' ); }, 1234 ); vs
  • 31.
    Problem #1 • Callbackas not last function argument foo( function() { someOtherFunction( function() { someFunctionWithCb( function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } }, 1, 'someString', ) }, 'a' ); }, 1234 ); foo( 1234, function() { someOtherFunction( 'a', function() { someFunctionWithCb( 1, 'someString', function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } } ) } ); } ); vs
  • 32.
    Problem #1 • Callbackas not last function argument foo( function() { someOtherFunction( function() { someFunctionWithCb( function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } }, 1, 'someString', ) }, 'a' ); }, 1234 ); foo( 1234, function() { someOtherFunction( 'a', function() { someFunctionWithCb( 1, 'someString', function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } } ) } ); } ); vs
  • 33.
    Problem #1 • Callbackas not last function argument foo( function() { someOtherFunction( function() { someFunctionWithCb( function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } }, 1, 'someString', ) }, 'a' ); }, 1234 ); foo( 1234, function() { someOtherFunction( 'a', function() { someFunctionWithCb( 1, 'someString', function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } } ) } ); } ); vs
  • 34.
    Problem #1 • Callbackas not last function argument foo( function() { someOtherFunction( function() { someFunctionWithCb( function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } }, 1, 'someString', ) }, 'a' ); }, 1234 ); foo( 1234, function() { someOtherFunction( 'a', function() { someFunctionWithCb( 1, 'someString', function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } } ) } ); } ); vs
  • 35.
    Problem #1 • Callbackas not last function argument foo( function() { someOtherFunction( function() { someFunctionWithCb( function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } }, 1, 'someString', ) }, 'a' ); }, 1234 ); foo( 1234, function() { someOtherFunction( 'a', function() { someFunctionWithCb( 1, 'someString', function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } } ) } ); } ); vs
  • 36.
    Problem #1 • Callbackas not last function argument foo( function() { someOtherFunction( function() { someFunctionWithCb( function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } }, 1, 'someString', ) }, 'a' ); }, 1234 ); foo( 1234, function() { someOtherFunction( 'a', function() { someFunctionWithCb( 1, 'someString', function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } } ) } ); } ); vs
  • 37.
    Problem #1 • Callbackas not last function argument foo( function() { someOtherFunction( function() { someFunctionWithCb( function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } }, 1, 'someString', ) }, 'a' ); }, 1234 ); foo( 1234, function() { someOtherFunction( 'a', function() { someFunctionWithCb( 1, 'someString', function () { doSomething(); for (let i = 1; i < 100; ++i) { doSomethingElse(); } } ) } ); } ); vs
  • 38.
  • 39.
    let promise1 =new Promise(function (resolve, reject) { // ... });
  • 40.
    let promise1 =new Promise(function (resolve, reject) { // ... }); call on success
  • 41.
    let promise1 =new Promise(function (resolve, reject) { // ... }); call on success call on error
  • 42.
    let promise1 =new Promise(function (resolve, reject) { // ... }); call on success call on error 🕒 Pending
  • 43.
    let promise1 =new Promise(function (resolve, reject) { // ... }); call on success call on error 🕒 Pending ✅ Fulfilled
  • 44.
    let promise1 =new Promise(function (resolve, reject) { // ... }); call on success call on error 🕒 Pending ✅ Fulfilled ❌ Rejected
  • 45.
    let promise1 =new Promise(function (resolve, reject) { // ... }); call on success call on error 🕒 Pending ✅ Fulfilled ❌ Rejected Settled }
  • 46.
    let promise1 =new Promise(function (resolve, reject) { // ... }); call on success call on error promise1.then(() => /* something to do on success */) .catch(() => /* something to do on error */) 🕒 Pending ✅ Fulfilled ❌ Rejected Settled }
  • 47.
    function delay(ms) { returnnew Promise(resolve => { setTimeout(resolve, ms); }); }
  • 48.
    function delay(ms) { returnnew Promise(resolve => { setTimeout(resolve, ms); }); } const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
  • 49.
    function delayedHelloWorld() { delay(1000).then(()=> { console.log('Hello'); delay(1000).then( () => { console.log('World!'); } ) }) }
  • 50.
  • 52.
  • 53.
  • 54.
    function delayedHelloWorld() { delay(1000) .then(()=> { console.log('Hello'); return delay(1000); }) .then(() => { console.log('World!'); }) }
  • 55.
    function delayedHelloWorld() { delay(1000) .then(()=> { console.log('Hello'); return delay(1000); }) .then(() => { console.log('World!'); }) }
  • 56.
    function delayedHelloWorld() { delay(1000) .then(()=> { console.log('Hello'); return delay(1000); }) .then(() => { console.log('World!'); }) } sleep(1000); console.log('Hello'); sleep(1000); console.log('World!');
  • 57.
    function delayedHelloWorld() { delay(1000) .then(()=> { console.log('Hello'); return delay(1000); }) .then(() => { console.log('World!'); }) } sleep(1000); console.log('Hello'); sleep(1000); console.log('World!'); sleep(1000); console.log('Hello'); sleep(1000); console.log('World!');
  • 58.
    function delayedHelloWorld() { delay(1000) .then(()=> { console.log('Hello'); }) .then(() => delay(1000)) .then(() => { console.log('World!'); }) }
  • 59.
  • 60.
  • 61.
  • 62.
    function delayedHelloWorld() { returndelay(1000) .then(() => { console.log('Hello'); }) .then(() => delay(1000)) .then(() => { console.log('World!'); }) }
  • 63.
    function delayedHelloWorld() { returndelay(1000) .then(() => { console.log('Hello'); }) .then(() => delay(1000)) .then(() => { console.log('World!'); }) } delayedHelloWorld().then(() => { console.log('after hello world'); });
  • 64.
    function delayedHelloWorld() { returndelay(1000) .then(() => { console.log('Hello'); }) .then(() => delay(1000)) .then(() => { console.log('World!'); }) } delayedHelloWorld().then(() => { console.log('after hello world'); });
  • 65.
    Task #2 # Filename 1 note.txt ❌ 2 pancake.jpg ❌ 3 book.pdf ❌ 4 schema.png ❌
  • 66.
    Task #2 # Filename 1 note.txt ❌ 2 pancake.jpg ❌ 3 book.pdf ❌ 4 schema.png ❌ ✔ ✔ ✔
  • 67.
    Task #2 # Filename 1 note.txt ❌ 2 pancake.jpg ❌ 3 book.pdf ❌ 4 schema.png ❌ ✔ ✔ ✔ Remove selected
  • 68.
  • 69.
  • 70.
  • 71.
    Task #2 Column name id name filesystemPath Filetable class FileRepository { getByIds(ids) { // ... } delete(ids) { // ... } }
  • 72.
    Task #2 Column name id name filesystemPath Filetable class FileRepository { getByIds(ids) { // ... } delete(ids) { // ... } }
  • 73.
    Task #2 Column name id name filesystemPath Filetable class FileRepository { getByIds(ids) { // ... } delete(ids) { // ... } }
  • 74.
    function removeFiles(ids) { returnfilesRepository.getByIds(ids).then( fileEntities => { fileEntities.forEach( fileEntity => removeFileInFS(fileEntity.filesystemPath) ); filesRepository.delete(ids); } ); }
  • 75.
    function removeFiles(ids) { returnfilesRepository.getByIds(ids).then( fileEntities => { fileEntities.forEach( fileEntity => removeFileInFS(fileEntity.filesystemPath) ); filesRepository.delete(ids); } ); } function removeFileInFS(filePath) { return new Promise( (resolve, reject) => { fs.unlink(filePath, (err) => { if (err) { reject(err); return; } resolve(); }); } ) }
  • 76.
    function removeFiles(ids) { returnfilesRepository.getByIds(ids).then( fileEntities => { fileEntities.forEach( fileEntity => removeFileInFS(fileEntity.filesystemPath) ); filesRepository.delete(ids); } ); } function removeFileInFS(filePath) { return new Promise( (resolve, reject) => { fs.unlink(filePath, (err) => { if (err) { reject(err); return; } resolve(); }); } ) } const removeFileInFS = util.promisify(fs.unlink);
  • 77.
    function removeFiles(ids) { returnfilesRepository.getByIds(ids).then( fileEntities => { fileEntities.forEach( fileEntity => removeFileInFS(fileEntity.filesystemPath) ); filesRepository.delete(ids); } ); } function removeFileInFS(filePath) { return new Promise( (resolve, reject) => { fs.unlink(filePath, (err) => { if (err) { reject(err); return; } resolve(); }); } ) } const removeFileInFS = util.promisify(fs.unlink); require('fs').promises
  • 78.
    function removeFiles(ids) { returnfilesRepository.getByIds(ids).then( fileEntities => { fileEntities.forEach( fileEntity => removeFileInFS(fileEntity.filesystemPath) ); filesRepository.delete(ids); } ); }
  • 79.
    function removeFiles(ids) { returnfilesRepository.getByIds(ids).then( fileEntities => { fileEntities.forEach( fileEntity => removeFileInFS(fileEntity.filesystemPath) ); filesRepository.delete(ids); } ); } Problem: resolved before actual deletion occurs
  • 81.
  • 82.
  • 83.
    function removeFiles(ids) { returnnew Promise( resolve => {
  • 84.
    function removeFiles(ids) { returnnew Promise( resolve => {
  • 85.
    function removeFiles(ids) { returnnew Promise( resolve => { filesRepository.getByIds(ids).then(
  • 86.
    function removeFiles(ids) { returnnew Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) {
  • 87.
    function removeFiles(ids) { returnnew Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop();
  • 88.
    function removeFiles(ids) { returnnew Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) {
  • 89.
    function removeFiles(ids) { returnnew Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) { filesRepository.delete(ids).then(resolve)
  • 90.
    function removeFiles(ids) { returnnew Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) { filesRepository.delete(ids).then(resolve) return;
  • 91.
    function removeFiles(ids) { returnnew Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) { filesRepository.delete(ids).then(resolve) return; }
  • 92.
    function removeFiles(ids) { returnnew Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) { filesRepository.delete(ids).then(resolve) return; }
  • 93.
    function removeFiles(ids) { returnnew Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) { filesRepository.delete(ids).then(resolve) return; } return removeFileInFS(currentFile).then(
  • 94.
    function removeFiles(ids) { returnnew Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) { filesRepository.delete(ids).then(resolve) return; } return removeFileInFS(currentFile).then( () => removeFilesInFS(fileEntities)
  • 95.
    function removeFiles(ids) { returnnew Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) { filesRepository.delete(ids).then(resolve) return; } return removeFileInFS(currentFile).then( () => removeFilesInFS(fileEntities) );
  • 96.
    function removeFiles(ids) { returnnew Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) { filesRepository.delete(ids).then(resolve) return; } return removeFileInFS(currentFile).then( () => removeFilesInFS(fileEntities) ); }
  • 97.
    function removeFiles(ids) { returnnew Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) { filesRepository.delete(ids).then(resolve) return; } return removeFileInFS(currentFile).then( () => removeFilesInFS(fileEntities) ); } );
  • 98.
    function removeFiles(ids) { returnnew Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) { filesRepository.delete(ids).then(resolve) return; } return removeFileInFS(currentFile).then( () => removeFilesInFS(fileEntities) ); } ); }
  • 99.
    function removeFiles(ids) { returnnew Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) { filesRepository.delete(ids).then(resolve) return; } return removeFileInFS(currentFile).then( () => removeFilesInFS(fileEntities) ); } ); } );
  • 100.
    function removeFiles(ids) { returnnew Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) { filesRepository.delete(ids).then(resolve) return; } return removeFileInFS(currentFile).then( () => removeFilesInFS(fileEntities) ); } ); } ); }
  • 101.
    function removeFiles(ids) { returnnew Promise( resolve => { filesRepository.getByIds(ids).then( function removeFilesInFS(fileEntities) { const currentFile = fileEntities.pop(); if (!currentFile) { filesRepository.delete(ids).then(resolve) return; } return removeFileInFS(currentFile).then( () => removeFilesInFS(fileEntities) ); } ); } ); } Problem: bad readability
  • 102.
    async function removeFiles(ids){ const fileEntities = await filesRepository.getByIds(ids); for (const fileEntity of fileEntities) { await removeFileInFS(fileEntity.filesystemPath); } await filesRepository.delete(ids); }
  • 103.
    async function removeFiles(ids){ const fileEntities = await filesRepository.getByIds(ids); for (const fileEntity of fileEntities) { await removeFileInFS(fileEntity.filesystemPath); } await filesRepository.delete(ids); } Problem: sequenced
  • 104.
    function removeFiles(ids) { letnumberOfFilesToDelete = ids.length; return new Promise( resolve => { filesRepository.getByIds(ids).then( fileEntities => { fileEntities.forEach(fileEntity => { removeFileInFS(fileEntity.filesystemPath).then( () => { numberOfFilesToDelete--; if (numberOfFilesToDelete === 0) { filesRepository.delete(ids).then(resolve); } } ) }); } ); } ); }
  • 105.
    function removeFiles(ids) { letnumberOfFilesToDelete = ids.length; return new Promise( resolve => { filesRepository.getByIds(ids).then( fileEntities => { fileEntities.forEach(fileEntity => { removeFileInFS(fileEntity.filesystemPath).then( () => { numberOfFilesToDelete--; if (numberOfFilesToDelete === 0) { filesRepository.delete(ids).then(resolve); } } ) }); } ); } ); } Problem: bad readability
  • 106.
  • 107.
  • 108.
    function removeFiles(ids) { returnfilesRepository.getByIds(ids) .then( fileEntities => { const removedFiles = fileEntities.map( fileEntity => removeFileInFS(fileEntity.filesystemPath) ); return Promise.all(removedFiles); } ) .then(() => filesRepository.delete(ids)) }
  • 109.
    async function removeFiles(ids){ const fileEntities = await filesRepository.getByIds(ids) await Promise.all( fileEntities.map(fileEntity => removeFile(fileEntity.filesystemPath)) ); await filesRepository.delete(ids); }
  • 110.
    async function removeFiles(ids){ const fileEntities = await filesRepository.getByIds(ids) await Promise.all( fileEntities.map(fileEntity => removeFile(fileEntity.filesystemPath)) ); await filesRepository.delete(ids); } Problem: what if some file removal fails?
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
    async function removeFiles(ids){ const fileEntities = await filesRepository.getByIds(ids); const removedFilesStatus = await Promise.allSettled( fileEntities.map(fileEntity => removeFile(fileEntity.filesystemPath).then( () => fileEntity.id ) ) ); const removedFiles = removedFilesStatus .filter(({ status }) => status === 'fulfilled') .map(({ value }) => value); await filesRepository.delete( ids.filter(id => removedFiles.includes(id)) ); if (removedFiles.length !== fileEntities.length) { throw Error(`Couldn't remove some files.`); } }
  • 116.
  • 117.
    Promise.race const someRequestWithTimeout =(url, timeoutMs) => Promise.race([ fetch(url), delay(timeoutMs).then(() => Promise.reject('TIMEOUT')) ]);
  • 118.
  • 119.
  • 120.
    Tips 📜Keep code "flat" ⛓Waitfor side-effects, benefit from promise chaining
  • 121.
    Tips 📜Keep code "flat" ⛓Waitfor side-effects, benefit from promise chaining ☣Do not mutate state outside "then"
  • 122.
    Tips 📜Keep code "flat" ⛓Waitfor side-effects, benefit from promise chaining ☣Do not mutate state outside "then" 3Use Promise combinators for multiple promises
  • 123.
    Tips 📜Keep code "flat" ⛓Waitfor side-effects, benefit from promise chaining ☣Do not mutate state outside "then" 3Use Promise combinators for multiple promises 👓async/await can be more readable
  • 124.
  • 125.
  • 126.
  • 127.
    Drawback Promise.then == map+ flatMap 😎 Pragmatic
  • 128.
    Drawback Promise.then == map+ flatMap 😎 Pragmatic 🧐 Category theory compatible
  • 129.
    7 Single value8 Multiple values ⚡ Sync
  • 130.
    7 Single value8 Multiple values ⚡ Sync Value
  • 131.
    7 Single value8 Multiple values ⚡ Sync Value Array
  • 132.
    7 Single value8 Multiple values ⚡ Sync Value Array 🕑 Async
  • 133.
    7 Single value8 Multiple values ⚡ Sync Value Array 🕑 Async Promise
  • 134.
    7 Single value8 Multiple values ⚡ Sync Value Array 🕑 Async Promise Stream / Observable
  • 135.