0

I am trying to console using a callback function, which itself is a callback function inside a setTimeout function. How can I get an orderly result?

I wish to have callback for f1 called, followed by f2 and finally f3, irrespective of their timer.

let f1 = (cb) => { 
    setTimeout(() => {
        cb(null, {a: 1})
    },100)
}

let f2 = (cb) => { 
    setTimeout(() => {
        cb(null, {a: 2})
    },50)
}

let f3 = (cb) => { 
    setTimeout(() => {
        cb(null, {a: 3})
    },10)
}

function parallelConsole(arr, cb) {
    let result = [];
    arr.forEach(func => {
        func((temp, val) => {
            console.log(val)
            result.push(val)
            if(arr.length === result.length) {
                cb(result)
            }      
        })
    })
}

parallelConsole([f1, f2, f3], (res) => {
    console.log(res) //[{a: 3}, {a: 2}, {a: 1}]
})

Expected Result: [{a: 1}, {a: 2}, {a: 3}]

Actual Result: [{a: 3}, {a: 2}, {a: 1}]

  • Would you parellelize the executions or would you execute them sequentially? f3's timeout is shorter than f2's one that's shorter than the f'3 timeout. The result is obvious f3,f2,f1. – Alessandro Aug 05 '19 at 13:11
  • @Alessandro Yeah, I want the result to be f1,f2,f3. That's why I am here. – Sbagaria2710 Aug 05 '19 at 13:13
  • Possible duplicate of [Call async/await functions in parallel](https://stackoverflow.com/questions/35612428/call-async-await-functions-in-parallel) – StefanN Aug 05 '19 at 13:14
  • @Alessandro Irrespective of timer, I want to have f1 at result[0]. – Sbagaria2710 Aug 05 '19 at 13:22
  • @StefanN Here, I did as mentioned in the link you shared. I'm still not getting the expected result. Link: https://jsfiddle.net/0v923pLq/1/ – Sbagaria2710 Aug 05 '19 at 13:29

4 Answers4

1

If you really have to work with callbacks and if this is for a production-related project, is using async.js an option? It provides some really useful functions (and .each might be what you are looking for).

I wish to have callback for f1 called, followed by f2 and finally f3, irrespective of their timer.

I'm a bit confused by this. If there's a timeout, then the callback will be called after the specified time has passed. Or do you want to console.log the result in an ordered manner (1, 2, 3) irrespective of when the callback has been called?

Edit: I've fixed a bit your code, so I think it does what you want. The promise from f1, f2, f3 wasn't returned. Also, as pointed in the comments, using Promise.all can ensure that the final callback is called after all promises have been fulfilled.

const f1 = (cb) => {
  return new Promise ((resolve) => setTimeout(() => {
    resolve(cb(null, { a: 1 }))
  }, 100))
};

const f2 = (cb) => {
  return new Promise ((resolve) => setTimeout(() => {
    resolve(cb(null, { a: 2 }))
  }, 50))
};

const f3 = (cb) => {
  return new Promise ((resolve) => setTimeout(() => {
    resolve(cb(null, { a: 3 }))
  }, 10))
};

function parallelConsole(arr, cb) {
  return Promise.all(arr)
    .then((values) => {
      cb(null, values);
    });
}

const log = (err, data) => {
  if (err) {
    // TODO: handle properly
    return console.log(err);
  }

  return data;
};

parallelConsole([f1(log), f2(log), f3(log)], (err, res) => {
  console.log(res)
});
Denny
  • 116
  • 1
  • 6
  • Hi Denny, welcome to SO! I understand that you do not have enough points for a comment, but is indeed not an answer. – B--rian Aug 06 '19 at 10:24
  • It'd be even better to just go all out on Promises and ditch the `cb` here :) – AKX Aug 06 '19 at 11:29
1
let f1 = (cb) => {
  setTimeout(() => {
    cb(null, { a: 1 });
  }, 100);
};

let f2 = (cb) => {
  setTimeout(() => {
    cb(null, { a: 2 });
  }, 50);
};

let f3 = (cb) => {
  setTimeout(() => {
    cb(null, { a: 3 });
  }, 10);
};

function parallelConsole(arr, cb) {
  let result = [];
  let offset = 0;

  const next = () => {
    if (arr.length === result.length) {
      return cb(result);
    }
    arr[offset]((temp, val) => {
      console.log(val);
      result.push(val);
      offset += 1;
      next();
    });
  };
  next();
}

parallelConsole([f1, f2, f3], (res) => {
  console.log(res); //[{a: 3}, {a: 2}, {a: 1}]
});
Jayson LP Chen
  • 121
  • 1
  • 3
0

If you're willing to go all out on Promises instead of callbacks, the code becomes somewhat more elegant:

// Promisified delay. Resolves with the given optional value after msec.
const delay = (msec, value) =>
  new Promise(resolve => setTimeout(() => resolve(value), msec));

// Call `cb` on the resolved value of `p`; resolve with the same value.
const tapPromise = (p, cb) =>
  p.then(v => {
    cb(v);
    return v;
  });

// Factories for the slow promises
const f1 = () => delay(100, { a: 1 });
const f2 = () => delay(50, { a: 2 });
const f3 = () => delay(10, { a: 3 });

// Create three promises and tap them with console.log.
// (They start executing right away, not when they're waited on.)
const promises = [f1(), f2(), f3()].map(p => tapPromise(p, console.log));

// Wait for all three promises to resolve, then log the results.
// Promise.all guarantees the order is the same as with the original promises.
Promise.all(promises).then(results => {
  console.log(results);
});
AKX
  • 152,115
  • 15
  • 115
  • 172
0

I just did a little refactoring on your solution. Do not use the array push method as you need to maintain the order of output. Use array index to store the result like: result[index] = val; inside forEach.

let f1 = (cb) => { 
    setTimeout(() => {
        cb(null, {a: 1})
    },100)
}

let f2 = (cb) => { 
    setTimeout(() => {
        cb(null, {a: 2})
    },50)
}

let f3 = (cb) => { 
    setTimeout(() => {
        cb(null, {a: 3})
    },10)
}

function parallelConsole(arr, cb) {
    let result = [];
    arr.forEach((func, index) => {
        func((temp, val) => {
            result[index] = val;
            if(arr.length === result.length) {
                cb(result)
            }      
        })
    })
}

parallelConsole([f1, f2, f3], (res) => {
    console.log(res) //[{a: 1}, {a: 2}, {a: 3}]
})
Ashique Ansari
  • 562
  • 4
  • 14