Mastering the concept of promise chaining through this straightforward example

I'm struggling to implement a logic where I need to compare the user's password to a given password and handle different scenarios based on the comparison result. Here's what I need to achieve:

  • If the user doesn't exist, return undefined (HTTP 404)
  • If the password is incorrect, throw a ForbiddenError (HTTP 403)
  • If the user exists and the password matches, return the user (HTTP 200)

My first attempt at implementing this logic is messy and hard to read:

  @Post()
  login(
    @BodyParam('username', { required: true }) username: string,
    @BodyParam('password', { required: true }) plainPassword: string,
  ) {

    return this.userRepository.findOne({ username: username, enable: true })
      .then ((user: User | undefined) => {
        if (!user) {
          return undefined; // 404
        }

        return bcrypt.compare(plainPassword, user.password)
          .then(passwordMatch => {
            if (!passwordMatch) {
              throw new ForbiddenError('Authentication failed.'); // 403
            }

            return user; // 200
          });
      });
  }

In my second attempt, the implementation is not working as expected and always returns 'ok':

return this.userRepository.findOne({ username: username, enable: true })
  .then((user: User | undefined) => {
    if (!user) {
      return undefined; // 404
    }

    return bcrypt.compare(password, user.password);
  })
  .then(passwordMatch => {
    // This code block is always executed, even when the user is undefined.

    return 'ok';
  });

Answer №1

The handler labeled then at the conclusion is always executed (assuming the promises do not reject) because the initial promise resolves with undefined if the user is not found, or with a boolean if the user exists.

Your nested approach was acceptable. If returning user in a successful scenario is necessary, then that method is likely the best choice.

However, if the objective is to simply return 'ok' as shown in your second code illustration for a successful outcome, you have the option to streamline the process, albeit with the caveat of managing the undefined value that arises when the user cannot be located. It can also be noted that the presence of a value for user will always be undefined if the user is not found:

return this.userRepository.findOne({ username: username, enable: true })
  // The subsequent `then` conveys `undefined` if `user` is `undefined`, otherwise it relies on the promise from `compare`
  .then((user: User | undefined) => user && bcrypt.compare(password, user.password))
  .then(passwordMatch => {
    if (passwordMatch === undefined) {
      // No user
      return undefined;
    } else if (!passwordMatch) {
      // Incorrect password
      throw new ForbiddenError('Authentication failed.'); // 403
    } else {
      // Success
      return 'ok';
    }
  });

If the goal is to streamline the process and also yield user, then it is necessary to continue passing user to the subsequent handler:

return this.userRepository.findOne({ username: username, enable: true })
  .then((user: User | undefined) => {
    return !user
        ? {user} // results in {undefined}
        : bcrypt.compare(password, user.password)
            .then(passwordMatch => ({user, passwordMatch})); // *** Generating an object
  })
  .then(({user, passwordMatch}) => { // *** Employing destructuring
    if (user === undefined) {
      // No user
      return undefined;
    } else if (!passwordMatch) {
      // Incorrect password
      throw new ForbiddenError('Authentication failed.'); // 403
    } else {
      // Success
      return user; // 200
    }
  });

(The initial then handler could be a succinct arrow function like in the initial code snippet above, but it became somewhat convoluted.)

Answer №2

By using return undefined, you are causing the initial then() callback to result in a promise that resolves to undefined.

As a result, the subsequent then() callback will run with undefined as its argument.

To address this, consider updating the second callback to handle this scenario.

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

Integrate an input field with a copy function similar to the GitHub clone view

Can anyone help me create a view with a tooltip similar to the one on Github? You can see the example here: https://i.sstatic.net/iBSof.png I attempted to use CSS but couldn't quite replicate the exact UI. Here is my current CSS code: [tooltip] { ...

Link to the Vue Bootstrap Toast Message

I have integrated vue-bootstrap-toasts (demo) into my website to display Toasts. However, I am facing an issue with adding a link to the toast that should redirect to another page. this.$toast.success('Test is created', { href: "www.googl ...

Angular-dc bar graph failing to display properly

I'm having some trouble creating a bar chart using crossfilter, dc.js, and angular-dc. The rowchart is working properly, but the barchart is not displaying the bars. When I inspect the element in Chrome, I can see the values, and when I force focus, t ...

Is it feasible to achieve a full 100% screen width within a confined div using relative positioning?

Can a div expand to 100vw within a parent div that is relative and has a limited width, without losing its height in the document like it would if positioned absolute? Is it achievable with pure CSS or do I need some jQuery or JS? body { background: ...

Trigger a pop-up alert box when the jQuery event $(document).ready is fired within a Smarty template

I'm currently attempting to make a popup message display when the document is fully loaded. Although I have successfully integrated Google Maps on another page, this task seems to be more challenging. Below is the code snippet: <html> < ...

Struggling to set the value for a variable within an Angular factory?

When dealing with a variable as an array, I have no trouble pushing objects inside and retrieving the values within the controller. However, when trying to directly assign an object to that variable, I run into issues. If anyone can assist me in achieving ...

Despite making changes, the React Element continues to be rendered

I am a beginner in React and facing an issue with my simple app My aim is to implement a search functionality, but the problem arises when I search for an element - all search results are displayed After clearing the search box, all elements appear as in ...

Using React and Typescript: How do I properly type a button that occasionally uses "as={Link}"?

I've encountered a scenario where I have a versatile button component that can either function as a button or transform into a link for enhanced user experience by using to={Link}. The challenge arises when Typescript always interprets the button as a ...

Unable to dynamically attach a class in Vue.js

I have exhausted all possible variations of this issue. I meticulously followed the official Vue guides, consulted numerous stack overflow posts, and went through various tutorials. I experimented with different syntaxes, quotations, array structures, and ...

Discover one among numerous interfaces available for handling Promise responses

My API handler returns a promise of a type. The object returned can be one of the following interfaces, depending on the API response: export interface Event { statusCode: number } export interface CreateEvent extends Event { data: Object } export in ...

Difficulty with slideToggle and other jQuery animations arises when selecting specific elements

When elements are selected from the page using the following code: $('.offers')[count - 1].slideToggle(500); The slideToggle function stops working, along with any other animations. However, this code snippet does work: $('.offers')[ ...

Eliminating the glow effect, border, and both vertical and horizontal scrollbars from a textarea

Dealing with the textarea element has been a struggle for me. Despite adding decorations, I am still facing issues with it. The glow and border just won't disappear, which is quite frustrating. Could it be because of the form-control class? When I rem ...

Is it possible for transclusion to display content from external sources using *ngIf and <ng-content>?

In my Angular4 Project, I have come across this snippet of code: <div class="divider"></div> <ng-content select=".nav-toggle"></ng-content> Now, I am trying to figure out a way to display the divider only when there is content pr ...

Arranging by upcoming birthday dates

Creating a birthday reminder app has been my latest project, where I store names and birthdays in JSON format. My goal is to display the names sorted based on whose birthday is approaching next. Initially, I considered calculating the time until each pers ...

What is causing the component to render three times?

Below is the structure of my component: import { useEffect, useState } from "react"; function Counter() { const [count, setCount] = useState(0); console.log("comp run"); const tick = () => { setCount(count + 1); conso ...

Using $state.go within an Ionic application with ion-nav-view may cause unexpected behavior

I recently started working on an Ionic Tabs project. I have a button called "initiateProcess" that triggers some operations when clicked using ng-click. Within the controller, it performs these operations and then navigates to a specific state (tab.target) ...

What is the process for submitting a form in Laravel 5 with ajax?

Struggling with understanding how to create an ajax post in Laravel. I would like to display errors using jQuery after validation, but I'm unsure about accessing the object sent to my controller and what needs to be 'returned' in the control ...

A step-by-step guide on retrieving information from Material UI components and incorporating an onSubmit feature to transmit data to the backend server

I've recently started working with react/material-UI. While working on a project, I turned to youtube videos and various resources for guidance. I opted for material-UI due to its user-friendly nature. However, I'm currently facing a challenge ...

In anticipation of a forthcoming .then() statement

Here is a return statement I have: return await foo1().then(() => foo2()); I am wondering, given that both foo1 and foo2 are asynchronous functions, if the code would wait for the resolution of foo2 or just foo1? Thank you. ...

The alignment issue persists in HTML/CSS despite troubleshooting efforts

I am facing a challenge while attempting to center text within a modal window, despite my efforts the text remains uncentered. This is my HTML code: <div ng-init="modalCompassDir()"> <div class="myModal"> <img class='floor ...