0

Given the following code:

const ports = [null,3001];

function getPort(port)
{ return new Promise((resolve, reject) =>
  { // setTimeout to simulate getting port from OS
    setTimeout(() =>
    { if(port < 3010)
      { return reject();
      }
      return resolve();
    }, 2000);
  });
}

ports.reduce(async (acc, port, index) =>
{ if(!port)
  { return;
  }

  try
  {
    const avail = await getPort(port);
    return port;
  }
  catch(avail)
  { ports.push(++port);
  }
});

Why is the reduce function only called for elements null and 3001 and not 3002 even though at the end of the reduce call the Array ports is equal to [null, 3001, 3002]?

Before the last reduce call completes, the original array already has a new element so why is it not called for that element as well?

The aim of the code is to try and find an available port starting at a given number and increment until we find one, or we hit an upper limit.

There are other ways of doing this, but the reduce method seemed the most succinct until I hit this dead end.

1
  • 1
    reduce is not the correct tool if you A) Aren't doing anything with acc, and B) Aren't using the return value of reduce. The above is best done with a simple loop, not reduce. Commented Nov 9, 2018 at 10:37

1 Answer 1

3

Because that's how it's defined:

The range of elements processed by reduce is set before the first call to callbackfn. Elements that are appended to the array after the call to reduce begins will not be visited by callbackfn. If existing elements of the array are changed, their value as passed to callbackfn will be the value at the time reduce visits them; elements that are deleted after the call to reduce begins and before being visited are not visited.

(my emphasis)

But separately, because your callback is an async function using await on getPort before pushing the array, the reduce call completes before any call to push even occurs. reduce does its work synchronously, even if you hand it an async function. async functions return promises (if you look, acc on the second callback will be a promise).

The aim of the code is to try and find an available port starting at a given number and increment until we find one, or we hit an upper limit.

A simple loop would be...simpler:

async function findPort(start, max) {
    for (let port = start; port < max; ++port) {
        try {
            await getPort(port);
            return port;
        } catch (e) {
            // keep looking
        }
    }
    throw new Error(`No available port found starting at ${start} and stopping before ${max}`);
}

Live Example:

function getPort(port) {
    return new Promise((resolve, reject) => { // setTimeout to simulate getting port from OS
        setTimeout(() => {
            if (port < 3010) {
                return reject();
            }
            return resolve();
        }, 200);
    });
}

async function findPort(start, max) {
    for (let port = start; port < max; ++port) {
        try {
            await getPort(port);
            return port;
        } catch (e) {
            // keep looking
        }
    }
    throw new Error(`No available port found starting at ${start} and stopping before ${max}`);
}

(async () => {
    try {
        console.log("Trying 3001 through 3020");
        let port = await findPort(3001, 3020);
        console.log(`Got port ${port}`);
    } catch (e) {
        console.error(`Failed: ${e}`);
    }
    try {
        console.log("Trying 3001 through 3009");
        let port = await findPort(3001, 3009);
        console.log(`Got port ${port}`);
    } catch (e) {
        console.error(`Failed: ${e}`);
    }
})();

...but I couldn't quite get what the ports array was for, so...

Sign up to request clarification or add additional context in comments.

9 Comments

… and that's before you add in the async function and the fact ports.push(++port); doesn't run until after the reduce function has finished.
@Quentin - Indeed.
@T.J.Crowder what I was ultimately aiming for was making a change to this to allow the npm module to also accept a range (keeping the changes to a minimum so) not modifying the existing reduce flow.
@HaykoKoryun - It would have been useful if you'd included that information (and code) in the question. That function already accepts a range: Set port to an array of the ports you want it to try, and it will use the first available one. (Notice that that code does use the accumulator of the reduce and its return value; it's a fairly standard promise sequence idiom.)
@T.J.Crowder it does accept a range, however it's inefficient if you need to give it a range of say 200 ports. I wanted to change it so that you could give it a range as a String e.g. '3000 - 3200' and then dynamically alter the original array.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.