Skip to content

Instantly share code, notes, and snippets.

@TimMensch
Created November 8, 2018 01:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save TimMensch/ce75d232096f2cfd623cf28fd1da160e to your computer and use it in GitHub Desktop.
Save TimMensch/ce75d232096f2cfd623cf28fd1da160e to your computer and use it in GitHub Desktop.
A slightly more complex counterexample to the "Functional River" that would be much uglier in promise form.
// Easier to follow. Equally easy to test -- and with one fewer function in need of testing.
import { userInfo } from "os";
const Promise = require("bluebird");
const { hashStringAsync } = Promise.promisifyAll(require("./lib/crypto"));
const { logEventAsync } = Promise.promisifyAll(require("./lib/log"));
const { openAsync } = Promise.promisifyAll(require("./lib/db"));
const { TimeoutError, ValidationError, NotFoundError } = require("./errors");
const _openHandle = openAsync(); // FYI: Promises include memoization (caching) built into same API
module.exports = { auth };
/* auth is our main function */
async function auth({
username,
password,
_onTimeoutError = errorHandler,
_onNotFoundError = errorHandler,
_onValidationError = errorHandler,
}) {
try {
// No more reusing "password" as "hashed password".
// Still starting the hash ASAP and not waiting for it.
const hashedPasswordPromise = hashStringAsync(password);
authValidate({ username, password });
const { users, admin } = await getModels();
const user = await users.findOneAsync({
username,
// I would have renamed "password" here; bad form to have a field named
// password that's expecting a hash.
password: await hashedPasswordPromise, // Deferred await to when we need it.
});
// Inventing a side tributary: If a user is an admin, then grab their admin
// permissions.
if (user.admin) {
const admin = await admin.findOneAsync({ id: user.id });
user.adminPermissions = admin.permissions;
}
await userFound(user);
return userInfo;
} catch (e) {
// Left this in because Bluebird's extension for matching errors is cute.
Promise.reject(e)
.catch(TimeoutError, _onTimeoutError)
.catch(NotFoundError, _onNotFoundError)
.catch(ValidationError, _onValidationError)
.catch(errorHandler);
}
}
function authValidate({ username, password }) {
if (!username || username.length < 4) {
throw new ValidationError("Invalid username. Required, 4 char minimum.");
}
if (!password || password.length < 4) {
throw new ValidationError("Invalid password. Required, 4 char minimum.");
}
}
function userFound(results) {
if (results) return;
throw new NotFoundError("No users matched. Login failed");
}
function getModels() {
return _openHandle.then(({ models }) => models);
}
function errorHandler(err) {
console.error("Failed auth!", err);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment