0

I am trying to create a notification app and here is my code firstly:

function sendNotification(messageUser, messageText, messageDepartment, messageTopic, userIDs) {
 var userAndroid = [];
 var userIOS = [];
 var userDevice;
 console.log(userIDs);
 for (var i = 0; i < userIDs.length; i++) {
  searchRegisterDevices(userIDs[i], function(result) {
   if (result) {
    for (var j = 0; j < result.length; j++) {
     userDevice = {platform: result[j].platform, token: result[j].token};
     if (userDevice.platform == 'android') {
      userAndroid.push(userDevice);  
     } else {
      userIOS.push(userDevice);
     }  
    }
   } else {
    console.log("ERROR");
   }
  });
 }
 
 console.log(userAndroid);
 if (userAndroid.length > 0) { 
   }

My issue is, while I am "for looping" gathering my devices, my code continues on to the sending part and fails because userAndroid is empty. What can I do to fix this callback hell as they say? I need to wait until my for loop is finished and then move on to the sending of the message. Any help would be appreciated!

Austin Hunter
  • 394
  • 6
  • 22
  • This is not callback hell, you're not nesting any callbacks to arbitrary depths here. – Bergi Sep 24 '16 at 22:05
  • Just use a recursive approach instead of the loop. – Bergi Sep 24 '16 at 22:06
  • Can you help me figure out how to keep my code from continuing? – Austin Hunter Sep 24 '16 at 22:06
  • What is `searchRegisterDevices`? Is the second argument, which is a function, supposed to be Asynchronous? If so, the `result` argument may take longer to pass from the Server, than it takes for the code below to execute, therefore `userAndroid.length === 0`. – StackSlave Sep 25 '16 at 01:34

2 Answers2

0

The problem here is that you're performing Asynchronous requests within a for loop, without handling the Asynchronosity. You are mixing the synchronous for loop and the (presumably)asynchronous searchRegisterDevices. Your loop variables will continue to increment till they reach their limit. So, by the time your callback function is popped from the event stack, the value of userIDs[i] is userIDs[userIDs.length], thanks to lexical scoping of javascript variables

What you need to do here is one of these three patterns:

Create Your Own Function Closure Using an IIFE

var j = 10;
for (var i = 0; i < j; i++) {
    (function(cntr) {
        // here the value of i was passed into as the argument cntr
        // and will be captured in this function closure so each
        // iteration of the loop can have it's own value
        asychronousProcess(function() {
            console.log(cntr);
        });
    })(i);
}

Create or Modify External Function and Pass it the Variable

If you can modify the asychronousProcess() function, then you could just pass the value in there and have the asychronousProcess() function the cntr back to the callback like this:

var j = 10;
for (var i = 0; i < j; i++) {
    asychronousProcess(i, function(cntr) {
        console.log(cntr);
    });
}

Use ES6 let

If you have a Javascript execution environment that fully supports ES6, you can use let in your for loop like this:

const j = 10;
for (let i = 0; i < j; i++) {
    asychronousProcess(function() {
        console.log(i);
    });
}

Here, of course, asychronousProcess represents your searchRegisterDevices service call.

Community
  • 1
  • 1
nikjohn
  • 20,026
  • 14
  • 50
  • 86
0

Here is how I would do it using babel and newer JS syntax:

import rfpify from 'rfpify';
const findDevice = rfpify(searchRegisteredDevices);

async function sendNotification({message, toUserIDs}) {
  let devices = await Promise.all(toUserIDs.map(id=>findDevice(id)));

  devices = devices.map( ({platform,token}) => { return {platform,token} } );

  const androidUsers = devices.filter(d => d && d.platform == 'android');
  const iOSUsers = devices.filter(d => d && d.platform != 'android');

  return {message, androidUsers, iOSUsers};
}

To set up babel and rfpify: npm i -S rfpify; npm i -D babel-cli babel-preset-latest babel-plugin-transform-runtime. Create a src where code goes and lib for output program which you run with node. Put this in .babelrc: { "presets": [ "latest"], "plugins": [["transform-runtime"]] } Inside of package.json "scripts": "build": "babel src -d lib" Build with npm run build.

Note: this assumes you are on Node ~6.3.1. You can use nvm to install/set Node version.

You will want to take your time to make sure you are 100% familiar with callbacks, then learn promises, then study how async/await and babel works. It will take a bit of time to get really used to it all.

Jason Livesay
  • 6,317
  • 3
  • 25
  • 31