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

Can the V8 JavaScript engine made by Google be used on iOS devices?

Is V8 compatible with iOS? If not, what embeddable JavaScript engine would you suggest? UPDATE: We will only be using it for internal scripting purposes, rather than in combination with HTML rendering. ...

Sending postMessage during the beforeunload event does not work as expected

When postMessage() is triggered within the beforeunload window event in an Ionic 2 browser, I've noticed that the message doesn't make it to the parent. However, if the same message is sent during the unload or load event, it is received successf ...

Angular-datatables has a scroller function that allows for easy navigation within the content of

I've been scouring the web for hours, but I can't seem to find a solution. Can someone please assist me in resolving this issue? Issue: I integrated angular-datatables within an md-tab using Angular material. Everything was running smoothly unti ...

Failure to trigger a follow-up function in webSQL transaction's success callback

Review the code below. I have called setQuestion() within the successCallBack of db.transaction but am encountering an error: Uncaught TypeError: this.setQuestions is not a function. Can you spot any issues in my code? game.module( "game.scenes.scene" ) ...

The AngularJS modal is sending back the results before updating the parent scope

When launching a modal from my web page, I am updating an array passed from the parent. However, when closing the modal and sending back the updated results, the parent scope object is also being updated. If the user decides not to update and cancels the ...

Encountering an issue: The API call for /api/orders was resolved but no response was sent, potentially causing requests to become stuck in Next.js

Struggling with an error while working on an ecommerce website using Next.js. The error message reads: error: API resolved without sending a response for /api/orders, this may result in stalled requests The error also indicates: API handler should not ...

Using ngBindHtml in combination with angularUI in a template may yield a different outcome compared to using $sce.parseAs

Edit: Problem solved! Details at the end of the question. Within my directive sfNgFieldWrapper, I have integrated a tooltip from angularjUI ui-bootstrap. The text for the tooltip is designated by tooltip="{{ttpText}}". The issue arises when the text con ...

Issue with Passing 'key' Prop to React Component in a Mapped Iteration

I'm struggling with adding a key prop to a React component in a mapped array within a Next.js project. The issue lies in a Slider component and an array of Image components being mapped. Even though I have provided a unique key for each element in the ...

Implementing Multiple HTML Files Loading in QUnit

Currently, I am utilizing QUnit for unit testing JavaScript and jQuery. The structure of my HTML document is as follows: <!DOCTYPE html> <html> <head> <title>QUnit Test Suite</title> <script src="../lib/jquery.js">< ...

The coordinates of the event do not match the coordinates of the location. Successful AJAX response data

How can I retrieve the accurate latitude and longitude when the Google Maps marker finishes dragging? It's confusing because for the same exact point, two different (but very approximate) coordinates are provided. Here are some example results for t ...

The issue arises when Node.js fails to identify the input fields that were dynamically inserted into the form

I came across a question similar to mine, but I found it challenging to apply the solution to node js. In my project, users can add items to a cart by clicking on them, which are then dynamically added to a form using jquery. However, upon submission, only ...

It appears that the options attribute is being overlooked by the Angular toast directive

While working with Angular, I found the need for toast messages. In my search for answers, I came across an Angular Toast solution discussed in this question: Fire notification toaster in any controller angularjs However, when trying to set the location ...

Struggling to construct a binary tree as my descendants are not arranged in the right sequence

I am currently working on building a binary tree using PHP, MySQL, and a jQuery plugin developed by Frank-Mich. Here is the progress I have made so far... DATABASE STRUCTURE CREATE TABLE IF NOT EXISTS `members` ( `id` int(11) NOT NULL AUTO_INCREMENT, ...

Is there a way to verify the existence of an Array Field?

For JavaScript, the code would look like this: if (array[x] != undefined && array[x][y] != undefined) { array[x][y] = "foo"; } Is there an equivalent simple method for achieving this in Java? I have tried checking if the field is null or no ...

Whenever Ionic is paired with LokiJS, it consistently results in the error message: "ionic.bundle.js:26794 TypeError: Cannot read property 'insert' of undefined"

Having faced numerous issues with SQLite, I decided to explore LokiJS as an alternative solution for my Ionic app. However, even with LokiJS, I encountered challenges. Currently, I have a simple code that should function smoothly: .controller('Proje ...

Converting a Unix timestamp to a formatted date string in Firebase: A step-by-step guide

Is there a way to change a timestamp from 1333699439 to the format 2008-07-17T09:24:17? For now, I have been utilizing Firebase.ServerValue.TIMESTAMP for timestamps in Firebase. ...

Animating progress bars using React Native

I've been working on implementing a progress bar for my react-native project that can be used in multiple instances. Here's the code I currently have: The progress bar tsx: import React, { useEffect } from 'react' import { Animated, St ...

Ensure the calling object is retained during the resolution of an Angular promise

Identifying the Issue One issue arises when resolving promises in Javascript: the context switches to Window #. This means that referring back to the object resolving the promise becomes tricky because I can't access or modify its variables. The com ...

Unit testing promises in Angular using Jasmine

My goal is to create a unit test that demonstrates the process of combining two promises using $q.all in Angular, and then confirming that both promises are resolved simultaneously. describe("Application controller", function() { var scope; var ...

Tips for responding to a chat conversation via email

Looking to implement a feature where when one user initiates a conversation in the chat, an email is automatically sent out to notify other chat users. The recipient can then reply directly from their email and have it added to the chat conversation. I a ...