Promises are so passé - Tim Perry - Codemotion Milan 2016
- 3. "Any fool can write code that a computer can understand.
Good programmers write code that humans can understand."
— Martin Fowler
- 14. PATTERN: SEQUENTIAL DEPENDENT OPERATIONS
- Get the user from the database
- Get that user's best friend
- Get that best friend's profile picture
- Add a mustache to everybody in the picture
- Show the transformed picture on the page
- 15. PATTERN: SEQUENTIAL DEPENDENT OPERATIONS
function mustachify(userId, callback) {
loadUserFromDatabase(userId, function (err, user) {
if (err) callback(err);
else user.getBestFriend(function (err, friend) {
if (err) callback(err);
else friend.getBestPhoto(function (err, photo) {
if (err) callback(err);
else addMustache(photo, function (err, betterPhoto) {
if (err) callback(err);
else showPhotoToUser(user, betterPhoto, callback);
});
});
});
});
}
- 16. PATTERN: CROSS-OPERATION ERROR HANDLING
function mustachify(userId, callback) {
try {
loadUserFromDatabase(userId, function (err, user) {
if (err) callback(err);
else {
try {
user.getBestFriend(function (err, friend) {
if (err) callback(err);
else {
try {
friend.getBestPhoto(function (err, photo) {
if (err) callback(err);
else {
try {
addMustache(photo, function (err, betterPhoto) {
if (err) callback(err);
else {
try {
showPhotoToUser(user, betterPhoto, callback);
} catch (e) { callback(e) }
}
});
} catch (e) { callback(e) }
}
});
} catch (e) { callback(e) }
}
});
} catch (e) { callback(e) }
}
});
} catch (e) { callback(e) }
}
- 19. PROMISES RECAP
Define a new promise:
var p = new Promise(function (resolve, reject) {
// ... Do some asynchronous things
// Eventually call resolve or reject
});
Promise.resolve({ myResult: 123 });
Promise.reject(new Error("BAD THINGS"));
- 20. PROMISES RECAP
Chain promises together:
loadData().then(function (data) {
return transformData(data);
}).then(function (transformedData) {
showData(data);
});
- 21. PROMISES RECAP
Catch errors across steps:
loadData().then(function (data) {
return transformData(data);
}).then(function (transformedData) {
showData(data);
}).catch(function (error) {
console.log(error);
});
- 22. PROMISES RECAP
Combine promises:
var allData = Promise.all([
loadData("company-one"),
loadData("company-two")
]);
var firstResponse = Promise.race([
getDataFromDataCenter1(),
getDataFromDataCenter2()
]);
- 26. PATTERN: COMBINING SEQUENTIAL RESULTS
- Get the current user's data
- Load the timeline for that user
- Show the user's details & timeline on screen
- 27. PATTERN: COMBINING SEQUENTIAL RESULTS
var user;
getUser().then(function (userData) {
user = userData; // <- Ick
return getTimeline(user);
}).then(function (timeline) {
showUser(user);
showTimeline(timeline);
});
- 28. PATTERN: ASYNC IN A LOOP
- For each task:
- Run the task to completion
- Add the result to the task results array
- Move on to the next task
- Return the array of task results
- 29. PATTERN: ASYNC IN A LOOP
function runTasks(tasks) {
var accumulatedPromise = Promise.resolve([]);
for (var task of tasks) {
accumulatedPromise.then(function (accumulatedResults) {
return task.run().then(function (result) {
return accumulatedResults.concat(result);
});
});
}
return accumulatedPromise;
}
- 30. PATTERN: CONDITIONAL ON ASYNC RESULT
- If the server is up and the user's login is valid:
- Save their data
- Show a nice message
- Otherwise:
- Show an error
- 31. PATTERN: CONDITIONAL ON ASYNC RESULT
isServerAvailable().then(function (serverIsAvailable) {
return serverIsAvailable && isAuthenticationValid();
}).then(function (canSave) {
if (canSave) {
return saveData().then(function () {
showMessage("Data saved");
});
} else {
showWarning("Couldn't save data");
}
});
- 38. function* generateEveryNumber() {
var n = 0;
while(true) {
yield n;
n += 1;
}
}
var allNumbers = generateEveryNumber();
allNumbers.next(); // { value: 0, done: false }
allNumbers.next(); // { value: 1, done: false }
allNumbers.next(); // { value: 2, done: false }
- 39. function* generateEveryNumber() {
var n = 0;
while(true) {
yield n;
n += 1;
}
}
for (var number of generateEveryNumber()) {
if (number > 1000) break;
console.log(number); // 1, 2, 3 ... 1000
}
- 40. function* sumUpNumbers() {
var accumulator = (yield);
while (true) {
var nextAddition = (yield accumulator);
accumulator += nextAddition;
}
}
var sum = sumUpNumbers();
sum.next(); // Need an initial next(), to run to first yield.
sum.next(1); // { value: 1, done: false }
sum.next(5); // { value: 6, done: false }
- 42. PATTERN: CONDITIONAL ON ASYNC RESULT
- If the server is up and the user's login is valid:
- Save their data
- Show a nice message
- Otherwise:
- Show an error
- 43. PATTERN: CONDITIONAL ON ASYNC RESULT
spawn(function* () {
if ((yield isServerAccessible()) &&
(yield isAuthenticationValid())) {
yield saveData();
showMessage("Data saved");
} else {
showWarning("Couldn't save data");
}
});
- 44. WRAPPING A GENERATOR
function spawn(generatorConstructor) {
var generator = generatorConstructor();
function step(input) {
var result = generator.next(input);
var value = Promise.resolve(result.value);
if (result.done) return value;
else return value.then(step);
}
return step();
}
// (Error handling omitted)
- 45. PATTERN: CONDITIONAL ON ASYNC RESULT
spawn(function* () {
if ((yield isServerAccessible()) &&
(yield isAuthenticationValid())) {
yield saveData();
showMessage("Data saved");
} else {
showWarning("Couldn't save data");
}
});
- 49. PATTERN: CONDITIONAL ON ASYNC RESULT
async function save() {
if ((await isServerAccessible()) &&
(await isAuthenticationValid())) {
await saveData();
showMessage("Data saved");
} else {
showWarning("Couldn't save data");
}
}
- 50. PATTERN: COMBINING SEQUENTIAL RESULTS
async function showUserAndTimeline() {
var user = await getUser();
var timeline = await getTimeline(user);
showUser(user);
showTimeline(timeline);
}
- 51. PATTERN: ASYNC IN A LOOP
async function runTasks(tasks) {
var results = [];
for (task of tasks) {
results.push(await task.run());
}
return results;
}
- 54. ASYNC/AWAIT ERROR HANDLING
async function mustachify(userId) {
var user = await loadUserFromDatabase(userId);
var friend = await getBestFriend(user);
var photo = await friend.getBestPhoto();
var betterPhoto = await addMustache(photo);
return showPhotoToUser(user, betterPhoto);
}
mustachify(userId).catch(function (error) {
// You can still use catch() - it's promises under the hood
});
- 55. ASYNC/AWAIT ERROR HANDLING
async function mustachify(userId) {
try {
var user = await loadUserFromDatabase(userId);
var friend = await getBestFriend(user);
var photo = await friend.getBestPhoto();
var betterPhoto = await addMustache(photo);
return showPhotoToUser(user, betterPhoto);
} catch (error) {
// Try/catch now works with promises & async too
}
}
mustachify(userId);
- 56. GOTCHA: CAN'T AWAIT IN NESTED FUNCTIONS
async function getAllFriendNames() {
var friendData = friendIds.map(function (friendId) {
// This code isn't in an async function,
// so this is a syntax error.
return (await getUserData(friendId));
});
return friendData.map(function (friend) {
return friend.name;
});
}
- 57. GOTCHA: CAN'T AWAIT IN NESTED FUNCTIONS
async function getAllFriendNames() {
var friendData = friendIds.map(async function (friendId) {
return (await getUserData(friendId));
});
// Map() doesn't care about async, so our getUserData
// calls haven't actually finished yet.
return friendData.map(function (friend) {
return friend.name;
});
}
- 58. GOTCHA: CAN'T AWAIT IN NESTED FUNCTIONS
async function getAllFriendNames() {
var friendData = await Promise.all(
friendIds.map(function (friendId) {
return getUserData(friendId);
})
);
return friendData.map(function (friend) {
return friend.name;
});
}