-
-
Save chrisngobanh/c30d86f2d53ad295023210cd5ea34ffb to your computer and use it in GitHub Desktop.
Introduction to Async/Await for Chegg Frontend Roundtable
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// To introduce Async Functions (Async/Await), we first need to go back to the discussion | |
// about synchronous vs asynchronous JavaScript and the benefits and downsides to both. | |
// The following example is a synchronous function call. | |
// The problem with synchronous operations is that they block the entire thread from | |
// doing more operations (JavaScript is single threaded). | |
// If it took 1 second to read that file, that means that you would not be handling events | |
// for a minute (on the browser-side, pretending that fs.readFileSync exists on the browser), | |
// and not handling requests (for Node.js). | |
// That is bad. | |
var data = fs.readFileSync('./big-file.json'); | |
console.log(data); // Will execute after the big file has been read |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// To do error handling for synchronous operations, you have to surround your code | |
// with try/catches, otherwise you'll get an "Unhandled Exception" if the operation throws an error. | |
// In addition, if you have completely unrelated operations that don't care if the | |
// previous statement has finished, then they will be forced to wait. | |
try { | |
var data = fs.readFileSync('./big-file.json'); | |
console.log(data); | |
unrelatedOperation(); // Called after the console.log | |
} catch (e) { | |
console.error(e); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Here is the same example, but done asynchronously. | |
// After the file is finished being read, the callback function will be called. | |
// You can handle the error in the callback, which is nice because your code won't | |
// automatically throw an error. | |
// In addition, your completely unrelated operation can run without waiting for the | |
// file to be read. | |
fs.readFile('./big-file.json', function(err, data) { | |
if (err) { | |
console.error(err); | |
} else { | |
console.log(data); | |
} | |
}); | |
unrelatedOperation(); // Called before the console.log |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// What if you have one operation that depends on another one that is asynchronous? | |
// (In other words, how do you "chain" two operations together where one starts after | |
// the other finishes?) | |
// If you try to do this, the second operation will execute before the completion | |
// of the first one, which is incorrect. | |
fs.readFile('./big-file.json', function(err, data) { | |
if (err) { | |
console.error(err); | |
} else { | |
console.log(data); | |
} | |
}); | |
// WRONG!!! | |
// This operation depends on the completion of the above operation, but it will be | |
// called before the above operation finishes | |
fs.unlinkFile('./big-file.json', function(err) { | |
if (err) { | |
console.error(err); | |
} | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Here is the previous problem, but done correctly, where you have one operation | |
// that depends on the completion of the previous asynchronous operation. | |
fs.readFile('./big-file.json', function(err, data) { | |
if (err) { | |
console.error(err); | |
} else { | |
console.log(data); | |
fs.unlinkFile('./big-file.json', function(err) { | |
if (err) { | |
console.error(err); | |
} | |
}); | |
} | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// What if you repeatedly need to chain operations together? | |
// Then you will get a mess like this. | |
// This is a contrived example of a downside of asynchronous JavaScript; | |
// chaining operations together makes the code very ugly and hard to follow. | |
// This is where asynchronous JavaScript can go haywire. | |
// This is callback hell. | |
function handleUserCreate(req, res) { | |
var username = req.body.username; | |
var email = req.body.email; | |
var password = req.body.password; | |
UserModel | |
.findOne({ username: username }, function(err, _user1) { | |
if (err) { | |
res.send(err.message); | |
} else if (_user1) { | |
res.send('Username already taken.'); | |
} else { | |
// Check if email is taken | |
UserModel | |
.findOne({ email: email }, function(err, _user2) { | |
if (err) { | |
res.send(err.message); | |
} else if (_user2) { | |
res.send('Email already taken.'); | |
} else { | |
// Bcryptify the password | |
bcrypt.hash(password, 10, function(err, hash) { | |
if (err) { | |
res.send(err.message); | |
} else { | |
// Generate an activation ID | |
crypto.randomBytes(2048, function(err, nonce) { | |
if (err) { | |
res.send(err.message); | |
} else { | |
var activationId = nonce.toString('base64'); | |
// Check to make sure the activation id doesn't already exist in the DB. | |
UserModel | |
.findOne({ activationId: activationId }, function(err, user) { | |
if (err) { | |
res.send(err.message); | |
} else if (user) { | |
res.send('Activation Id already exists.'); | |
} else { | |
UserModel.create({ | |
username: username, | |
email: email, | |
password: hash, | |
activationId: activationId | |
}, function(err, user) { | |
if (err) { | |
res.send(err.message); | |
} else { | |
res.send('Your account has been created!'); | |
} | |
}) | |
} | |
}) | |
} | |
}) | |
} | |
}); | |
} | |
}); | |
} | |
}) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Because of this issue, 'Promises' were invented. | |
// Promises were created to help manage your asynchronous flow easier. | |
// They allow you to chain methods very clearly, and has a catch-all error | |
// handling at the very end if any of the operations throw an error. | |
// This is definitely a lot nicer to look at, faster to code, and easier to | |
// maintain than the previous example. | |
// However, there's a bit of a learning curve because you need to both understand | |
// how Promises work and have a relatively strong JavaScript background. | |
// Promises was recently introduced as a new feature in ES6, but have existed | |
// long before and you can use them through libraries like Bluebird and Q. | |
// In addition, jQuery 1.8+ supports promises. | |
function handleUserCreate(req, res) { | |
var username = req.body.username; | |
var email = req.body.email; | |
var password = req.body.password; | |
var hash; | |
var activationId; | |
// Check if username is taken | |
UserModel.findOne({ username: username }) | |
.then(function(user) { | |
if (user) throw new Error('Username already taken.'); | |
// Check if email is taken | |
return UserModel.findOne({ email: email }); | |
}) | |
.then(function(user) { | |
if (user) throw new Error('Email already taken.'); | |
return bcrypt.hash(password, 10); | |
}) | |
.then(function(_hash) { | |
hash = _hash; | |
return crypto.randomBytes(2048); | |
}) | |
.then(function(nonce) { | |
activationId = nonce.toString('base64'); | |
// Check to make sure the activation id doesn't already exist in the DB. | |
return UserModel.findOne({ activationId: activationId }); | |
}) | |
.then(function(user) { | |
if (user) throw new Error('Activation Id already exists.'); | |
return UserModel.create({ | |
username: username, | |
email: email, | |
password: hash, | |
activationId: activationId | |
}); | |
}) | |
.then(function(user) { | |
res.send('Your account has been created!'); | |
}) | |
.catch(function(err) { | |
res.send(err.message); | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This is the previous example, but cleaned up using ES6 features, such as object shorthand. | |
// This shows how the JavaScript syntactic sugar can make your code a lot nicer. | |
function handleUserCreate(req, res) { | |
const { username, email, password } = req.body; | |
let password; | |
let activationId; | |
// Check if username is taken | |
UserModel.findOne({ username }) | |
.then((user) => { | |
if (user) throw new Error('Username already taken.'); | |
// Check if email is taken | |
return UserModel.findOne({ email }); | |
}) | |
.then((user) => { | |
if (user) throw new Error('Email already taken.'); | |
return bcrypt.hash(password, 10); | |
}) | |
.then((hash) => { | |
password = hash; | |
return crypto.randomBytes(2048); | |
}) | |
.then((nonce) => { | |
activationId = nonce.toString('base64'); | |
// Check to make sure the activation id doesn't already exist in the DB. | |
return UserModel.findOne({ activationId }); | |
}) | |
.then((user) => { | |
if (user) throw new Error('Activation Id already exists.'); | |
return UserModel.create({ username, email, password, activationId }); | |
}) | |
.then((user) => { | |
res.send('Your account has been created!'); | |
}) | |
.catch((err) => { | |
res.send(err.message); | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Introducing Async Functions, syntactic sugar on Promises. | |
// This is the previous example, but now using Async/Await. | |
// To use Async Functions, you need to define your functions using the `async` keyword. | |
// Notice how our asynchronous operations now look very synchronous, except they're not. | |
// Instead, it is only execution of the function that "blocks". | |
// When the function sees the `await` keyword, it will: | |
// 1. Halts the execution of the function. | |
// 2. Wait for the Promise to the right of the `await` to return its Promise Value. | |
// 3. Replaces the `await X` with the returned Promise Value. | |
// 4. Continues the execution of the function. | |
async function handleUserCreate(req, res) { | |
const { username, email, password } = req.body; | |
try { | |
// Check if username is taken | |
const isUsernameTaken = await UserModel.findOne({ username }); | |
if (isUsernameTaken) throw new Error('Username already taken.'); | |
// Check if email is taken | |
const isEmailTaken = await UserModel.findOne({ email }); | |
if (isEmailTaken) throw new Error('Email already taken.'); | |
const password = await bcrypt.hash(password, 10); | |
const nonce = await crypto.randomBytes(2048); | |
const activationId = nonce.toString('base64'); | |
// Check to make sure the activation id doesn't already exist in the DB. | |
const isActivationIdTaken = await UserModel.findOne({ activationId }); | |
if (isActivationIdTaken) throw new Error('Activation Id already exists.') | |
await UserModel.create({ username, email, password, activationId }); | |
res.send('Your account has been created!'); | |
} catch (e) { | |
res.send(e.message); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// One way to think of Async Functions is: they are another way of writing Promises. | |
// Async Functions return Promises themselves. In addition, returning a value in an | |
// Async Function would be the same as a Promise returning a Promise Value. | |
// This means that you could essentially replace all of your Promises with Async Functions. | |
// The following two 'helloWorld's (async function and promise) are functionally equivalent. | |
// I'll let you make your own judgement on which one you'd rather read and write the code for. | |
// Async/Await is a feature in ES2017 that was recently approved. | |
// You can use it today in Babel using their 'es2017' preset. | |
async function helloWorld() { | |
return 'Hello World!'; | |
} | |
function helloWorld() { | |
return new Promise((fulfill, reject) => fulfill('Hello World!')); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment