Skip to content

Instantly share code, notes, and snippets.

@chrisngobanh
Last active January 28, 2017 23:54
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chrisngobanh/c30d86f2d53ad295023210cd5ea34ffb to your computer and use it in GitHub Desktop.
Save chrisngobanh/c30d86f2d53ad295023210cd5ea34ffb to your computer and use it in GitHub Desktop.
Introduction to Async/Await for Chegg Frontend Roundtable
// 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
// 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);
}
// 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
// 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);
}
});
// 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);
}
});
}
});
// 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!');
}
})
}
})
}
})
}
});
}
});
}
})
}
// 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 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);
});
}
// 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);
}
}
// 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