Javascript exceptions shouldn’t be thrown when an IO to error occurs. Instead, helpful messages should be sent your your logger system AND to the user.
Method 1: Try/Catch
Traditionally, these exceptions are handled using Try/Catch blocks, but those can easily make your code unreadable. For example,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
const getCart = async id => ({items: [{productId: 1}]}); const getProduct = async id => ({imageId: 1}); const getImage = async id => {throw new Error("Image ID Not Found");} const getCartFull = async (cartId) => { let cart, products = {}, images = {}; try { cart = await getCart(cartId); for (let item of cart.items) { try { products[item.productId] = await getProduct(item.productId); try { let imageId = products[item.productId].imageId; images[imageId] = await getImage(imageId); } catch (error) { return [error]; } } catch (error) { return [error]; } } } catch (error) { return [error]; } return [null, cart, products, images]; } getCartFull(1).then(res => console.dir(res)); |
Method 2: Catch Callback
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
const getCart = async id => ({items: [{productId: 1}]}); const getProduct = async id => ({imageId: 1}); const getImage = async id => {throw new Error("Image ID Not Found");} const getCartFull = async (cartId) => { let cart, products = {}, images = {}, error; cart = await getCart(cartId).catch(e => error = e); if(error) return [error]; for (let item of cart.items) { products[item.productId] = await getProduct(item.productId).catch(e => error = e); if(error) return [error]; let imageId = products[item.productId].imageId; images[imageId] = await getImage(imageId).catch(e => error = e) if(error) return [error]; } return [null, cart, products, images]; } getCartFull(1).then(res => console.dir(res)); |
Method 3: Custom Exception Wrapper
This method is inspired by Dima Grossman’s post.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
const catcher = (promise) => { return promise.then(data => { return [null, data]; }) .catch(err => [err]); } const getCart = async id => ({items: [{productId: 1}]}); const getProduct = async id => ({imageId: 1}); const getImage = async id => {throw new Error("Image ID Not Found");} const getCartFull = async (cartId) => { let cart, products = {}, images = {}, error; [error, cart] = await catcher(getCart(cartId)); if(error) return [error]; for (let item of cart.items) { [error, products[item.productId]] = await catcher(getProduct(item.productId)); if(error) return [error]; let imageId = products[item.productId].imageId; [error, images[imageId]] = await catcher(getImage(imageId)); if(error) return [error]; } return [null, cart, products, images]; } getCartFull(1).then(res => console.dir(res)); |
Which to Choose?
Method 1 is just plain yuck. Method 2 is decent, I prefer Method 3 for readability.