A function that recursively calls itself and uses a promise for iteration

In my current setup, I have a function called emailIterator that reads messages from a stack and sends them one by one using an emailer. The process is done recursively by calling emailIterator with fewer messages each time as they are sent.


import { emailer } from 'pigeon';
 
interface MessageObject {
  email: string;
  recipientName: string;
  message: string;
}
 
interface ErrorFormat {
  error: any;
  messageAtError: MessageObject;
}
 
const data: MessageObject[] = [
  {
    email: '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="ddbfb2bf9db9b2b0bcb4b3f3beb2b0">[email protected]</a>',
    recipientName: 'Bob',
    message: 'Lorem ipsum dolor sit amet mattis.',
  },
  {
    email: '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="7c1615113c1813111d1512521f1311">[email protected]</a>',
    recipientName: 'Jim',
    message: 'Lorem ipsum dolor sit amet mattis.',
  },
];

const emailIterator = (messages: MessageObject[], cb: any): void => {
  if (messages.length === 0) {
    return cb(undefined, 'All messages sent');
  } else {
    const toSend = messages.pop();
 
    emailer(toSend)
      .then(() => {
        return emailIterator(messages, cb);
      })
      .catch((e) => {
        return cb({
          error: e,
          messageAtError: toSend,
        });
      });
  }
};

emailIterator(data, (err?: ErrorFormat, msg?: string) => {
  if (err) {
    console.log('There was an error with a message:', err.messageAtError);
    console.error(err.error);
    return;
  }
  console.log(msg);
});

I'm looking to convert this recursive solution into an iterative one. Here's my attempt so far. Does it handle errors efficiently? Can it be improved?


const emailIterator2 = async (messages: MessageObject[], cb: any): void => {
  for(let i = messages.length - 1; i >= 0; i--) {
    let toSend = messages[i]; 
    try {
      const result = await emailer(toSend); 
    } catch (e: ErrorFormat) {
      return cb({
        error: e, 
        messageAtError: toSend
      })
    }
    if(i === 0) {
      return cb(undefined, 'All messages sent')
    }
  }
}

Answer №1

Implement the use of async..await within a for..of loop:

const emailIterator = async (messages: MessageObject[], cb: any): void => {
  let toSend
  try {
    for (const m of messages) {
      toSend = m
      await emailer(m)
    }
    cb(undefined, "All messages sent")
  }
  catch (e) {
    cb({ error: e, messageAtError: toSend })
  }
}

It is advised to avoid mixing callbacks with promises as it can be cumbersome. Consider converting emailIterator into a promise-based function:

const emailIterator = async (messages: MessageObject[]): Promise<string> => {
  let toSend
  try {
    for (const m of messages) {
      toSend = m
      await emailer(m)
    }
    return "All messages sent";
  }
  catch (e) {
    e.messageAtError = toSend;
    throw e;
  }
}

Take note of the unusual assignment of toSend so that it can be utilized in the catch block. An even better approach would be for the emailer function to handle errors properly -

async function emailer(message): Promise<void> {
  try {
    // ...
  }
  catch (e) {
    e.messageAtError(message);
    throw e;
  }
}

By following this pattern, emailIterator no longer needs to manage this concern. The functionality remains consistent, eliminating the need for explicit try..catch blocks and ensuring that errors propagate correctly. Additionally, TypeScript effortlessly deduces the return type of Promise<string> -

async function emailIterator(messages: MessageObject[]) {
  for (const m of messages)
    await emailer(m)
  return "All messages sent";
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The name 'Map' cannot be located. Is it necessary to alter your target library?

After running the command tsc app.ts, an error occurs showing: Error TS2583: 'Map' is not recognized. Should the target library be changed? Consider updating the lib compiler option to es2015 or newer. I want the code to compile without any issu ...

Transmitting chosen option using AJAX and JavaScript

I have a select dropdown that I want to use to send a selected option to AJAX and then display it. Here is my HTML: <label>Tag</label> <select id="day"></select> <label>Monat</label> <select id="month"&g ...

The use of jQuery for fetching posts via ajax can lead to a crash in the browser

I have a social media platform where I implemented jQuery on the main feed page. The jQuery is set up so that as users scroll down, the next batch of posts is fetched using ajax and added to the DOM. However, after a few ajax requests, the browser slows do ...

Unique TypeScript code snippets tailored for VSCode

Is it possible to create detailed custom user snippets in VS Code for TypeScript functions such as: someArray.forEach((val: getTypeFromArrayOnTheFly){ } I was able to create a simple snippet, but I am unsure how to make it appear after typing an array na ...

Using ElectronJS requires the usage of the import keyword to load ES Modules

I've recently delved into Electron development by exploring the Electron Docs. I opted for ES6 syntax with import/export, while the documentation showcased the use of require. To align with ES Module standards, I updated my package.json file with typ ...

React App Creation: Issue with ESLint configuration in TypeScript environment

I recently built a React app with the CRA (typescript template), but I noticed that TypeScript isn't adhering to the rules specified in the ESLint configuration. This is puzzling because I have consistently used this configuration in all my React proj ...

Issue with JSON parsing on non-Chrome web browsers

Encountering a problem with parsing fetched JSON data from browsers other than Chrome, Firefox providing error message: "SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data". Notably, code functions in local node.js environmen ...

"Utilize Regular Expressions to conceal part of a text string with a

Looking for a way to conceal part of a string using JavaScript? For example, wanting to mask the second and third segments of a credit card number like this using regex: 4567 6365 7987 3783 → 4567 **** **** 3783 3457 732837 82372 → 3457 ****** 82372 ...

What sets apart npm correlation-id from uuid?

Can you please explain the distinction between the uuid and correlation-id npm packages? It seems that correlation-id actually utilizes the uuid package internally.. When would you recommend using correlation-id over uuid? Important: I am not utilizing ...

"Encountering issues with Firebase deployment related to function-builder and handle-builder while working with TypeScript

I encountered 4 errors while executing firebase deploy with firebase cloud functions. The errors are originating from files that I didn't modify. node_modules/firebase-functions/lib/function-builder.d.ts:64:136 - error TS2707: Generic type 'Req ...

Transferring an Object from one AngularJS Controller to Another

Currently delving into the world of AngularJS, I've encountered a seemingly trivial problem with no solution in sight. My issue lies in managing two lists/controllers created by a factory service. Specifically, I'm facing difficulties removing ...

Using remote: true with select_tag in Rails allows the select menu to dynamically

When I call an AJAX function from a select_tag like this: <%= select_tag 'quantity', options_from_collection_for_select(order.options), :quantity, :quantity, order.quantity), onchange: "update_price(#{order.id}, this.value);" %> Here is t ...

Novice in AngularJS routing

Having trouble with my first AngularJS routing code. The error console isn't much help. Here is my HTML page: <body ng-app="myApp"> <div ng-controller="AppController"> <div class="nav"> <ul> <li> ...

React-Markdown is throwing an error that states: "ES Module must be imported in order to load

Trying to integrate react-markdown into my Next.js project resulted in an immediate error when attempting to use it. Here is the code snippet: import React from 'react' import ReactMarkdown from 'react-markdown' export function Markd ...

Issues with Line Chart in D3: Scaling and Zoom not functioning as expected due to ClipPath limitations

I am utilizing D3 version 4 to process data and create a graph based on dates. Although I have successfully adjusted everything to be compatible with zoom functionality, I am struggling to prevent the line from extending beyond the chart axes. I would pre ...

What could be causing NestJS/TypeORM to remove the attribute passed in during save operation?

Embarking on my Nest JS journey, I set up my first project to familiarize myself with it. Despite successfully working with the Organization entity, I encountered a roadblock when trying to create a User - organizationId IS NULL and cannot be saved. Here ...

Guide to retrieving and editing images from Appwrite using Vue

Currently, I am utilizing a View frontend to retrieve a PDF file from Appwrite Storage. My goal is to acquire the PDF, insert additional text onto it, and then save it to the user's local device. Below is the code I have so far - although I can recei ...

Tips for identifying the category of a hyperlink within my javascript code?

Hello, I am currently working on a script that involves AJAX functionality: function ajax(){ if (navigator.standalone) return; for (var i= document.links.length; i-->0;) { document.links[i].onclick= function() { var req= new ...

Rotate camera around item when dragged

In my three.js scene, there is an object positioned at {x: 0, y: 0, z: -150}, while the camera is placed at {x: 0, y: 0, z: 75}. I am trying to allow the user to drag the camera around the object, keeping it in view at all times. https://i.sstatic.net/dp6 ...