Executing Promises in an Array through JavaScript

LIVE DEMO

Here is a function provided:

function isGood(number) {
  var defer = $q.defer();

  $timeout(function() {
    if (<some condition on number>) {
      defer.resolve();
    } else {
      defer.reject();
    }
  }, 100);

  return defer.promise;
}

A goal is to find the first "good" number in an array of numbers (e.g. [3, 9, 17, 26, 89]). This can be achieved using the following code:

var arr = [3, 9, 17, 26, 89];

findGoodNumber(arr).then(function(goodNumber) {
  console.log('Good number found: ' + goodNumber);
}, function() {
  console.log('No good numbers found');
});

The recursive version for this task can be found here: DEMO

function findGoodNumber(numbers) {
  var defer = $q.defer();

  if (numbers.length === 0) {
    defer.reject();
  } else {
    var num = numbers.shift();

    isGood(num).then(function() {
      defer.resolve(num);
    }, function() {
      findGoodNumber(numbers).then(defer.resolve, defer.reject)
    });
  }

  return defer.promise;
}

The question remains whether there is a better approach, possibly non-recursive, to achieve the same outcome.

Answer №1

Is there a more efficient solution?

Avoid using the deferred antipattern as it is not recommended!

function isGood(number) {
  return $timeout(function() {
    if (<some condition on number>) {
      return number; // Resolving with the number simplifies code below
    } else {
      throw new Error("…");
    }
  }, 100);
}
function findGoodNumber(numbers) {
  if (numbers.length === 0) {
    return $q.reject();
  } else {
    return isGood(numbers.shift()).catch(() => {
      return findGoodNumber(numbers);
    });
  }
}

Can we avoid recursion?

You can use a loop that chains multiple then calls, but recursion is suitable for this scenario. If you insist on avoiding recursion, you could try:

function findGoodNumber(numbers) {
  return numbers.reduce((previousFinds, num) => {
    return previousFinds.catch(() => {
      return isGood(num);
    });
  }, $q.reject());
}

However, this method is less efficient compared to the "recursive" version which evaluates lazily.

How about improving speed?

You can run all isGood checks in parallel and wait for the first one to be fulfilled. This approach may offer better performance, but consider potential unnecessary work. It's advisable to use a promise library with cancellation support.

An example implementation using the Bluebird library and its any helper function:

function findGoodNumber(numbers) {
  return Bluebird.any(numbers.map(isGood))
}

Answer №2

Here's an innovative approach using a unique type of recursion:

function checkFirst(arr){
    var index = 0;
    return $q.when().then(function execute(){
        if(index >= arr.length) return $q.reject(Error("No Number Found"));
        return validateNumber(arr[index++]).catch(execute);
    });
}

This solution is reminiscent of Bergi's method and provides an efficient alternative without requiring a Promise.reduce implementation found in some libraries like Bluebird and When.

Answer №3

this is a unique version created using the array.map function

Check out the Demo

angular.module('MyApp', []).run(function($q, $timeout) {
  var numbersArray = [3, 9, 17, 26, 89];

  findSpecialNumber(numbersArray).then(function(specialNumber) {
    console.log('Special number found: ' + specialNumber);
  }, function() {
    console.log('No special numbers found');
  });

  function findSpecialNumber(arrayOfNumbers) {
    var defer = $q.defer();

    arrayOfNumbers.forEach(function(num){      
      isSpecial(num).then(function(){
        defer.resolve(num);
      });

    });

    return defer.promise;
  }

  function isSpecial(number) {
    var defer = $q.defer();

    $timeout(function() {
      if (number % 2 === 0) {
        defer.resolve();
      } else {
        defer.reject();
      }
    }, 1000);

    return defer.promise;
  }
});

Answer №4

Promises were originally designed for asynchronous operations, but isGood() is using them as a boolean. Instead of just resolving or rejecting with a boolean value, the state of the promise itself is being used to convey information:

  • pending == unknown
  • resolved == true
  • rejected == false

While some may see this as unconventional use of promises, it can be fun to explore their capabilities in this way.

The main challenges with treating promises as booleans are:

  • 'true' will follow the success path and 'false' will follow the fail path
  • Promise libraries don't offer built-in support for boolean algebra operators like NOT, AND, OR, XOR

Until these aspects are further researched and documented, it requires creativity to leverage these features effectively.

Let's address this issue using jQuery, which I am more familiar with.

First, let's redefine isGood():

function isGood(number) {
    return $.Deferred(function(dfrd) {
        if(parseInt(number, 10) == number) {
            setTimeout(function() { dfrd.resolve(number); }, 100);//"true"
        } else {
            setTimeout(function() { dfrd.reject(number); }, 100);//"false"
        }
    }).promise();
}

We need a method to reverse the state of a promise, since jQuery promises do not have a native inversion function. Here's how you can implement that:

function invertPromise(p) {
    return $.Deferred(function(dfrd) {
        p.then(dfrd.reject, dfrd.resolve);
    });
}

Now, let's modify the findGoodNumber() function to utilize the revamped isGood() method and the invertPromise() utility:

function findGoodNumber(numbers) {
    if(numbers.length === 0) {
        return $.Deferred.reject().promise();
    } else {
        return invertPromise(numbers.reduce(function(p, num) {
            return p.then(function() {
                return invertPromise(isGood(num));
            });
        }, $.when()));
    }
}

You can then call the same routine with different data:

var arr = [3.1, 9.6, 17.0, 26.9, 89];
findGoodNumber(arr).then(function(goodNumber) {
    console.log('Good number found: ' + goodNumber);
}, function() {
    console.log('No good numbers found');
});

This demonstrates how promises can be manipulated to represent boolean values asynchronously.

Alternative Approach

A different approach to solving this problem without needing inversion is by performing an "OR-scan". This alternative solution using jQuery is:

function findGoodNumber(numbers) {
    if(numbers.length === 0) {
        return $.Deferred.reject().promise();
    } else {
        return numbers.reduce(function(p, num) {
            return p.then(null, function() {
                return isGood(num);
            });
        }, $.Deferred().reject());
    }
}

This offers another way to handle the problem without requiring explicit inversions.

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

Mastering the art of utilizing class attributes and functions from within modules

Currently, I am developing classes in ES6 modules using object literals and my intention is to update object attributes within a function. Although modules run in strict mode by default which ensures safe usage of this, I am uncertain whether calling the ...

Generating hierarchical structures from div elements

Looking for guidance on how to parse a HTML page like the one below and create a hierarchical Javascript object or JSON. Any assistance would be much appreciated. <div class="t"> <div> <div class="c"> <input t ...

What's Next? Redirecting Pages in Node.js Express after Handling POST Requests

Is it possible to redirect to a different page from a post request? module.exports = function(app) { app.post('/createStation', function(request, response){ response.redirect('/'); //I'm having trouble getting ...

Angular JS effectively prevents redundant data from being displayed to users when scrolling infinitely while also efficiently removing DOM elements for previous data

I'm currently working on implementing AngularJS Infinite Scroll without using jQuery. My goal is to display the first 3 data items when the page loads and then load the next 3 data items from a JSON array object as the user scrolls. The issue I am fac ...

Securely encoding information with PHP and decrypting it using JavaScript

I'm currently working with 2 servers. My goal is to generate a pair of keys, store the private key in local storage, and send the public key to my PHP server. The main objective is to encrypt data using the public key in PHP and decrypt it using Jav ...

Guide to integrating react-phone-number-input into material-ui TextField

Would it be possible for me to use a Material UI TextField component as the inputComponent prop for the PhoneInput component from react-phone-number-input? I am facing an issue where I am unable to apply the ref successfully. Even though I can see the Mat ...

Using ajax to call the Google Maps Api is proving to be ineffective

I am facing some issues with a website. On this particular webpage (), I am trying to display a Google map on the location page using an AJAX function. The getLocation.php file is being called by AJAX: <?php echo '<div id="map-canvas"></ ...

"Implementing jQuery for Efficient File Uploads with Ajax

Is it feasible to utilize the jQuery code below for executing a file upload via the POST method of an ajax request? $.ajax({ type: "POST", timeout: 50000, url: url, data: dataString, success: function (data) { alert('succe ...

Displaying a progress bar during image uploads in PHP without using AJAX

I am in the process of uploading a file on my website and it is working well. However, I would like to display a progress bar while the upload is taking place. I have considered using ajax for this purpose, but I am unable to use ajax. Is there a way to ac ...

The Restangular library seems to be struggling with handling query parameters

Currently, I am implementing Restangular in my Ionic application and trying to incorporate Infinite scrolling. To achieve this, I need to make a call using Restangular. As I refer to the Restangular documentation (here), I attempted the following approach ...

Utilizing a V-for/V-if loop to iterate through a multi-dimensional array and filter data based on date

I'm facing a challenge with setting up a v-for loop for an array I've constructed, which is already in the desired format Below is a snippet representing the data format. Essentially, I want to create a table for employees where the header consi ...

Is there a way for me to incorporate a feature that verifies whether an email address is already registered before allowing the person to sign up?

I am currently working with Node.js, express.js, mongoose, and pug to develop a registration/login system. I have successfully stored the name and email in a mongoose database with specified schema for these fields. The data is sent from a pug page via a p ...

Error: Unable to access the 'name' property of an undefined variable

When running the code, I encountered a "TypeError: Cannot read property 'name' of undefined" error, even though when I console.log it, it provides me with the object import React from "react"; class Random extends React.Component { constructo ...

Issues encountered when executing unit tests using karma

I encountered these issues in the logs. Seeking advice on how to proceed. Thank you. I've attempted uninstalling and reinstalling phantomjs, clearing out my node modules and bower component directories. Everything was functioning as expected before, a ...

Position the buttons in the react children's application

**Looking for help on positioning Black Widow in the center-left and Hulk at the bottom left of the screen. I'm having trouble figuring it out, does anyone have any tips? Also, I only want to isolate the buttons to each picture. Not sure if I need to ...

The function User.find does not exist and it is not possible to replace the `users` model after it has

Currently, I am experimenting with using mongoose, mongoDB, next, and express in a test project. Despite referencing solutions like Cannot overwrite model once compiled Mongoose and others, I am encountering issues unique to my situation. Upon initializat ...

What is the process for invoking a JavaScript function from the code-behind of an Asp.Net application?

Here is a sample of my JavaScript function : function NeedToExport() { alert('Time to export your data!'); } Additionally, in my ASP.NET code behind : Page.ClientScript.RegisterStartupScript(this.GetType(), "ExportKey", "NeedToExport();"); ...

How can we ensure that Ajax consistently provides useful and positive outcomes?

Here is my PHP code: function MailList() { $this->Form['email'] = $_POST["email"]; $index = $this->mgr->getMailList($_POST["email"]); } // SQL code function getMailList($email) { $mailArray = Array(); $sql ...

Is the risk of using deprecated synchronous calls on the main thread worth it in JavaScript?

I have created a single page web application that relies on modifying a section of the page and then calling specific functions to format this section after it has been modified. One of the crucial post-modification calls I need to make is MathJax. Howeve ...

Incorporating interactive buttons within Leaflet popups

I'm facing an issue with adding buttons to a Leaflet popup that appears when clicking on the map. My goal is to have the popup display 2 buttons: Start from Here Go to this Location The desired outcome looks like this sketch: ___________________ ...