I'm working on a project that involves the FCC API, but I'm struggling to grasp the flow of the code

Having some trouble with my code and seeking help to understand what's going wrong. I want to avoid copying and pasting, but rather comprehend my actions. The issue arises when creating a new Url object in the post route. It seems like the function getHighestShort doesn't wait for its promise to resolve, always returning shortMax as 1. If I log certain lines of code, I notice that the if statement inside the getHighestShort function is resolved after saving the Url object, causing it to always have short_url set to 1. Any hints or guidance would be greatly appreciated, as I've tried using await before the new Url creation to ensure it holds up the rest of the code. -42-year-old self-learner- This post might come across as scattered :)

require('dotenv').config();
const express = require('express');
const cors = require('cors');

const bodyParser = require('body-parser')
const mongoose = require('mongoose')
const Url = require('./schemas/urlModel');


mongoose.connect('mongodb://localhost:27017/urlshortener', { useNewUrlParser: true, useUnifiedTopology: true })
  .then(() => console.log('connection open'))
  .catch((err) => console.log(err))

const app = express();

// Basic Configuration
const port = process.env.PORT || 3000;


app.use(cors());

app.use('/public', express.static(`${process.cwd()}/public`));

app.use(bodyParser.urlencoded({ extended: false }))


app.get('/', function(req, res) {
  res.sendFile(process.cwd() + '/views/index.html');
});

// Your first API endpoint
//find input url in db and return the long version
app.get('/api/shorturl/:input', async (req, res) => {
  try {
    const {input} = req.params
    // const shortUrlNr = parseInt(input)
    console.log(input)
    console.log(Url.findOne( {short_url: `${input}`} ))
    const found = await Url.findOne( {short_url: `${input}`} )
    res.redirect(`https://${found.full_url}`)
  } catch (err) {
      console.log(err)
      console.log(res.status)
  }
})

app.post('/api/shorturl/new', async (req, res, next) => {
  try {
    const { url } = await req.body
    //check for valid url, if not respond with json object as asked in tests.
    if (!validURL(url)) {
      return res.json({error: 'invalid url'})
    } else {
      //find url in db and get json response with the corresponding object. 
      //If it is already in the db, than redirect to this website as asked in tests. 
        const foundUrl = await Url.findOne( {full_url: `${url}`} )
        if (foundUrl !== null) {
          res.redirect(`https://${foundUrl.full_url}`)
        } else {
        //if the url is not there, we''ll create a new entry with that url.
        const newUrl = await new Url( {full_url: `${url}`,  short_url: `${getHighestShort()}`} )
        await newUrl.save()
        res.json(newUrl)
        } 
    }
  } catch(err) {
    console.log('catch error post request')
    return next(err)
  }
})

app.listen(port, function() {
  console.log(`Listening on port ${port}`);
});

function validURL(str) {
  var pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol
    '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name
    '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address
    '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path
    '(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string
    '(\\#[-a-z\\d_]*)?$','i'); // fragment locator
  return !!pattern.test(str);
}

function getHighestShort() {
  let shortMax = 1
  //make an array from the collection with short values sorted descending
  //and than pick the first one which should be the highest value.
  //if the array is empty just return 1 as it is the first one
  Url.find({}).sort({short_url: 1}).exec()
    .then(res => {
      if (res.length !== 0) {
        shortMax = res[0].short_url + 1
      } 
    })
    .catch(err => {
      console.log('catch err server.js function getHighestShort')
      console.log(err)
    })
    return shortMax
}

model

const mongoose = require('mongoose')
const Schema = mongoose.Schema

const urlSchema = new Schema( 
    {
        full_url: {
            type: String,
            required: false
        },
        short_url: Number
    }
)

const Url = mongoose.model('Url', urlSchema)

module.exports = Url

Answer №1

When using the await operator, it waits for a promise to be resolved in order to function as expected.

For instance, in the line:

const { url } = await req.body

The use of await is unnecessary in this context since req.body is not a promise but an object. Therefore, it is equivalent to:

const { url } = req.body

If your code isn't working, let's first examine the getHighestShort function. Does it return a promise? (Spoiler alert: it does not).

To address this, we must modify the function so that it returns a promise (and subsequently wait for that promise to resolve).

Firstly, alter getHighestShort to return a promise:

function getHighestShort() {
    let shortMax = 1
    // Create an array from the collection with short values sorted in descending order
    // Then select the first value which should be the highest
    // If the array is empty, return 1 as the initial value
    return new Promise((resolve, reject) => {
        Url.find({}).sort({short_url: 1}).exec()
            .then(res => {
                if (res.length !== 0) {
                    shortMax = res[0].short_url + 1;
                }
                resolve(shortMax)
            })
            .catch(err => {
                console.log('Error caught in server.js function getHighestShort')
                console.log(err)
                // You can decide on error handling logic here
                resolve(shortMax)
            })
    })
}

I suggest refining this code for better readability, like so:

async function getHighestShort() {
    let shortMax = 1
    try {
        const results = await Url.find({}).sort({short_url: 1}).limit(1).exec()
        if (results.length !== 0) {
            shortMax = results[0].short_url + 1
        }
        return shortMax
    } catch (e) {
        console.log('Error caught in server.js function getHighestShort')
        console.log(e)
        return shortMax
    }
}

Note the addition of limit(1); there is no need to fetch the entire collection each time.

It's important to note that there are incorrect assumptions about the atomicity of operations. What if two requests are processed simultaneously? This could result in two URLs having the same short_url.

I'll leave this issue for you to handle independently.

Secondly, now that getHighestShort returns a promise, we must await its completion:

const short_url = await getHighestShort();
const newUrl = await new Url( {full_url: `${url}`,  short_url: short_url} )

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

Guide on accessing a nested array within a JSON object using JavaScript

I'm currently faced with a json object that contains nested arrays and names with spaces like Account ID. My goal is to only display the Account ID's within my Vue.js application. While I can access the entire response.data json object, I'm ...

Determining whether a PFUser is being created or updated with the AfterSave function

Struggling to differentiate between whether a PFUser is being updated or saved for the first time? I tried implementing a solution from parse.com here, but it's not working as expected. No matter what, I always end up with a false value, be it an init ...

Using jQuery to serialize parameters for AJAX requests

I could use some help figuring out how to set up parameters for a $.ajax submission. Currently, I have multiple pairs of HTML inputs (i pairs): > <input type="hidden" value="31" name="product_id"> <input > type="hidden" value="3" name="qua ...

Animating file transfer can be achieved using either JavaScript or CSS

My goal is to create a visual representation of file transfer, such as moving a file from one folder to another. I have successfully achieved this using JavaScript with the following code: <script type="text/javascript> var img; var animateR; var an ...

Some angles in three.js may cause faces to be obscured

When I work in Blender, my process usually involves creating straightforward models with colored materials. However, when I export these models to Collada format and import them into Three.js, I encounter an issue where some faces do not appear from cert ...

Can scrollHeight ever be less than clientHeight or offsetHeight?

Is it guaranteed that the output of Math.max(el.scrollHeight, el.offsetHeight, el.clientHeight) will always be equal to el.scrollHeight? Typically, clientHeight <= offsetHeight <= scrollHeight. While there are rare instances where clientHeight > ...

Ways to distinguish XmlHttpRequest Access-Control-Allow-Origin issues from regular network errors

When making an ajax request, there is a possibility of encountering an error, indicating a failure to establish communication with the intended target (no status code returned). To handle these errors, you can use the following code: var oXhr = new XMLHt ...

Vue.js: Utilizing async/await in Vue.js results in an observer being returned

Currently, I'm attempting to retrieve data from an API and store it in an array. The issue arises when I try to log the response data from the API - the data is displayed just fine. I assign the value of a variable to the data obtained from awaiting t ...

Unlock the Power of EmailJS with Vue.js 2 and TypeScript

I couldn't find a similar issue online, so here's my problem. I'm trying to create a form for receiving contact from an app using Vue.js 2 and TypeScript. Here is my code: <form ref="form" class="form-data" @submit.pr ...

What is the best method for transmitting multipart data video files from a NextJS frontend to an Express backend?

I have been struggling to enable users to upload video files and send them through my NextJS API to my Express backend for storage in an S3 bucket. Despite days of research, I can't seem to figure out a solution. I am aware that NextJS has limitations ...

Error: Attempting to access the properties `line_items.amount`, `line_items.currency`, `line_items.name`, `line_items.description`, or `line_items` is not allowed

Hi there, I'm currently working on creating an Amazon-inspired platform and I've encountered an error while trying to integrate Stripe with the project. Can anyone provide some assistance? You can refer to the video tutorial I'm using by fol ...

Bootbox Dialog Form

Looking to implement a functionality where a Bootbox dialog pops up with an "OK" button. Upon clicking the "OK" button, it should initiate a POST request, sending back the ID of the message to acknowledge that the user has read it. The Bootbox dialog fun ...

A guide to showcasing items based on their categories using React.js

After successfully displaying Categories from a port (http://localhost:5000), accessing my MongoDB database, I encountered an issue when attempting to display the products for each category separately. Despite trying the same method as before, I keep rec ...

Managing selected ticket IDs in a table with AngularJS

I have a table that includes options for navigating to the next and previous pages using corresponding buttons. When I trigger actions for moving to the previous or next page (via controller methods), I store the IDs of checked tickets in an array $scope. ...

Executing script once the DOM has completed loading

In my project, I'm dynamically generating menu items for a menu bar based on headers from external files that are imported using an XMLHttpRequest(). The menu bar is then updated as I navigate through different pages. Everything works smoothly. Each ...

Stopping the setTimeout function triggered by a click event in a Reactjs application

I'm a beginner with Reactjs and I ran into a dilemma while using setTimeOut. I couldn't figure out whether to use clearTimeOut or stopPropagation() to stop it. Here's my code: render: function() { return ( < div className = "colorCl ...

Creating intricate mazes using canvas drawing techniques

I recently developed a maze generator as a personal project utilizing a graph. While the generation logic works perfectly, I am facing challenges when it comes to rendering the maze. In my approach, each cell is represented by an array of 4 edges where the ...

What is the best method to incorporate extra parameters into highcharts using data extracted from datatables?

I have a query regarding the integration of datatables with highcharts. The issue I'm facing is related to specific configurations for chart-A, which is of column type. Even after applying the configurations, it seems like they are not being incorpora ...

Should I generate separate views in React for every table in my backend?

As a beginner in the world of React, I find myself grappling with its intricate structure. Currently, my backend consists of three tables that are powered by Express.js; however, the focus now shifts to creating a CRUD UI interface for these tables withi ...

Tips for circumventing the ajax data size restriction in asp.net mvc3

Currently, I am implementing an auto suggest function using AJAX in the following manner: $("#empName2").autocomplete({ search: function (event, ui) { var key = CheckBrowser(event); if (key == 13) return tr ...