DEV Community

Discussion on: Callback hell OR try catch hell (tower of terror)

Collapse
 
ovi profile image
Muhammad Ovi

But what if we have all the promise dependend on promise above them, and we want to handle error for each of them?

Collapse
 
peerreynders profile image
peerreynders • Edited
async function tryAll(promises, name) {
  const resolved = [];
  const rejected = [];
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
  const all = await Promise.allSettled(promises);
  for (const outcome of all)
    if (outcome.hasOwnProperty('value')) resolved.push(outcome.value);
    else rejected.push(outcome.reason);

  if (rejected.length < 1) return resolved;

  const error = new Error('tryAll encountered errors');
  error.name = name;
  error.rejected = rejected;
  throw error;
}

const ERROR_NAME_MULTI_REJECT = 'ErrorMultiReject';

function handleMultiReject(multiError) {
  for (const err of multiError.rejected)
    console.log('Multi Error', err.toString());
}

function routeError(err) {
  if (err.name === ERROR_NAME_MULTI_REJECT) handleMultiReject(err);
  else console.log('Other Error -', err.toString());
}

function makeError(source, name) {
  const error = new Error(`Error from ${source}`);
  error.name = name;
  return error;
}

const testFn = (result, source, name) =>
  result ? Promise.resolve(result) : Promise.reject(makeError(source, name));

async function main() {
  try {
    const one = await testFn('one', `testFnOne`, 'ErrorOne');
    const results = await tryAll(
      [
        testFn(null, `testFnTwo`, 'ErrorTwo'),
        testFn(null, `testFnThree`, 'ErrorThree'),
        testFn('four', `testFnFour`, 'ErrorFour'),
      ],
      ERROR_NAME_MULTI_REJECT
    );
    console.log('done');
  } catch (err) {
    routeError(err);
  }
}

main();
// "Multi Error", "ErrorTwo: Error from testFnTwo"
// "Multi Error", "ErrorThree: Error from testFnThree"
Enter fullscreen mode Exit fullscreen mode

Perhaps you meant something else - in which case chaining the error handlers may be worth considering:

// wrap the error with one that is uniquely named ...
async function wrapError(promise, name) {
  try {
    // https://jakearchibald.com/2017/await-vs-return-vs-return-await/#return-awaiting
    return await promise;
  } catch (err) {
    const error = new Error('Wrapped Error');
    error.name = name;
    error.wrapped = err;
    throw error;
  }
}

function makeChainedHandler(next, handler) {
  return next
    ? (err) => {
        handler(err);
        next(null);
      }
    : handler;
}

// ... so that the appropriate error handler
// to process the wrapped error can be retrieved
// which calls any other chained "aborted" handlers.  
//
function handleError(err) {
  if (err.hasOwnProperty('wrapped')) {
    const handler = handlers.get(err.name);
    if (handler) {
      handler(err.wrapped);
      return;
    }

    err = err.wrapped;
  }

  console.log('Other Error -', err.toString());
}

async function main() {
  try {
    const one = await wrapError(demoFnOne('one'), ERROR_NAME_ONE);
    const two = await wrapError(demoFnTwo(null), ERROR_NAME_TWO);
    const three = await wrapError(demoFnThree('three'), ERROR_NAME_THREE);
    const four = await wrapError(demoFnFour('four'), ERROR_NAME_FOUR);
    console.log('done');
  } catch (err) {
    handleError(err);
  }
}

// --- Begin Demo Support
const DEMO_ONE = 'DemoFnOne';
const DEMO_TWO = 'DemoFnTwo';
const DEMO_THREE = 'DemoFnThree';
const DEMO_FOUR = 'DemoFnFour';

const ERROR_NAME_ONE = 'ErrorOne';
const ERROR_NAME_TWO = 'ErrorTwo';
const ERROR_NAME_THREE = 'ErrorThree';
const ERROR_NAME_FOUR = 'ErrorFour';

const config = [
  [DEMO_ONE, ERROR_NAME_ONE],
  [DEMO_TWO, ERROR_NAME_TWO],
  [DEMO_THREE, ERROR_NAME_THREE],
  [DEMO_FOUR, ERROR_NAME_FOUR],
];

function makeError(fnName) {
  const error = new Error(`Error from ${fnName}`);
  error.name = 'Error' + fnName[0].toUpperCase() + fnName.slice(1);
  return error;
}

const makeDemoFn = (fnName) => (result) =>
  result ? Promise.resolve(result) : Promise.reject(makeError(fnName));

function makeErrorHandler(fnName) {
  return (err) => {
    if (err) {
      console.log(err.toString());
      return;
    }

    console.log(`Error: ${fnName} was aborted.`);
  };
}

function makeBoth([fnName, errName]) {
  const fn = makeDemoFn(fnName);
  const handler = makeErrorHandler(fnName);
  return [
    [fnName, fn],
    [errName, handler],
  ];
}

const [fns, handlers] = (() => {
  const fnEntries = [];
  const handlerEntries = [];
  let chained = null;

  // in reverse to set up the necessary abort chaining
  for (let i = config.length - 1; i >= 0; i -= 1) {
    const [fnEntry, [name, handler]] = makeBoth(config[i]);
    chained = makeChainedHandler(chained, handler);
    fnEntries.push(fnEntry);
    handlerEntries.push([name, chained]);
  }

  return [new Map(fnEntries), new Map(handlerEntries)];
})();

const demoFnOne = fns.get(DEMO_ONE);
const demoFnTwo = fns.get(DEMO_TWO);
const demoFnThree = fns.get(DEMO_THREE);
const demoFnFour = fns.get(DEMO_FOUR);

// --- End Demo Support

main();

// "ErrorDemoFnTwo: Error from DemoFnTwo"
// "Error: DemoFnThree was aborted."
// "Error: DemoFnFour was aborted."
Enter fullscreen mode Exit fullscreen mode