0

Requirement:

I need to write a function that takes an array of objects. Each item in the array has a state; if state === 'processing', a delay of 2 seconds is implemented before moving on to the next item. In other words, I actually need to block execution of the function for 2 seconds before looking at the next element in the array. When an element's state is not 'processing', the function returns an object and no further array elements are looked at.

The problem:

I can't think of any way to create a delay without using asynchronous approaches. I've written code (see below) which relies on Promise, but this does not satisfy the requirements, as with any asynchronous approach the Promise will return immediately; the delay only delays promise resolution not function execution. Thus, I wonder if there isn't a way of doing this synchronously, of blocking return of the function until after any delays are implemented.

My question:

Is there a way to accomplish this synchronously, to block execution of the code for a specified period of time before returning an object?

My code, which uses an asynchronous approach:

function wait(delay, callback, arg) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(callback(arg));
    }, delay);
  });
}

function getProcessingPage(data) {
  const currentObj = data[0];
  const state = currentObj.state;

  if (state === 'processing') {
    return wait(2000, getProcessingPage, data.slice(1));
  }

  return Promise.resolve({});
}

The solution:

Since this question has been closed (wrongly, in my opinion), it can not be answered, but I now have an answer. The answer is not performant in the least bit — it is not considered good practice and should never find its way into production code — but it does exactly what I needed to do for this exercise. The solution is to use while to prevent any code execution during the desired delay. See below:

function wait(delay) {
  const delayUntil = performance.now() + delay;
  while (performance.now() < delayUntil) {
    // do nothing, as the while statement blocks execution
  }
}

function getProcessingPage(data) {
  const currentObj = data[0];
  const state = currentObj.state;

  if (state === 'processing') {
    wait(2000);
    return getProcessingPage(data.slice(1));
  }

  return {};
}
Derek Henderson
  • 9,388
  • 4
  • 42
  • 71
  • No, this is impossible. you can't stop the thread and make it wait for your promise to resolve. You could make getProcessingPage async and await it, but then the function that uses it will have to be async, because getProcessingPage would be returning a promise. – Kevin B Oct 15 '19 at 19:15
  • So basically, you want `getProcessingPage()` to return a promise that resolves with a new object after X seconds, where X is 2 times the number of leading items in the array with `state === 'processing'`? – Patrick Roberts Oct 15 '19 at 20:12
  • @PatrickRoberts, I need `getProcessingPage()` to return an object, not a promise. I thought about just counting the number of "processing" objects that occur before the first non-"processing" object and multiplying that number by 2000 to get the total millisecond delay, but I think that isn't expected behaviour. – Derek Henderson Oct 15 '19 at 21:10
  • @KevinB, could I accomplish this using generators instead of promises? I'm not as familiar with generators as I ought to be, but I think maybe that's a possible solution. Your thoughts? – Derek Henderson Oct 15 '19 at 21:12
  • No, while they appear to be able to do stuff synchronously, outside of the generator (aka returning from it) still has to follow all the same rules. – Kevin B Oct 15 '19 at 21:13
  • ok, thanks, @KevinB. – Derek Henderson Oct 15 '19 at 21:21
  • I can add an answer with mobx observables, where you can add your logic to the autorun. It won't return an object, just track the last object added to a global array. Could this solution fit? – Karin C Oct 15 '19 at 22:20
  • @KarinC, no, but thank you. Either there is an answer that returns an object, or there isn't. – Derek Henderson Oct 15 '19 at 22:23
  • 1
    "*Or am I demonstrating my utter ignorance of how promises work?*" - no, not of promises specifically, but of **asynchronous** delays in general. You cannot immediately return an object which will be constructed in the future. A promise for it is the next best approximation, but your caller (that you `return` to) will need to wait for the future as well. – Bergi Oct 16 '19 at 02:15
  • @Bergi, what I was hoping is that there might be a synchronous way of implementing the delay, of blocking execution of the rest of the function until any delays were processed. That's why I thought generators might be a potential option. – Derek Henderson Oct 16 '19 at 12:01
  • No, there's no way to make a synchronous delay in JS (without busy waiting that keeps the cpu at 100% and the UI frozen). Generators don't make anything synchronous, they just provide a simpler syntax for running consecutive chunks of code asynchronously (in exactly the same way that modern `async`/`await` does). – Bergi Oct 16 '19 at 12:13
  • Thank you, @Bergi. Btw, I have edited my question to make more clear what I am asking, as I do not believe this to be a duplicate. – Derek Henderson Oct 16 '19 at 12:22
  • The duplicate flag set by @Bergi is correct. Even if the title of the linked question sounds like a different topic, the answer actually reaches it scope in explaining what asynchronous javascript is. Give it a check and ask again – Christian Vincenzo Traina Oct 16 '19 at 12:23
  • @CristianTraìna, I realise my question may not have been clear, which is why I've edited it, but again I need to emphasize that I am not asking how to handle asynchronicity. Rather, I am asking if there is a way to accomplish this synchronously. This is a very different question than the linked question. I want to block execution of the script for a specified length of time before returning an object. I know I can create a method which returns a promise, but is there a way to force the function to wait? I don't need to wait for a response, just literally wait for a specified period of time. – Derek Henderson Oct 16 '19 at 12:55
  • 1
    @DerekHenderson The answer to that question was given in the comments: It's a simple **No**, there is no way to do that. I did close the question because I don't think it warrants an answer post, and the duplicates do actually help you with your underlying problem and explain *why* it is not possible. – Bergi Oct 16 '19 at 13:35
  • So, there *is* a way to do that! I thought so! If I do `const then = performance.now() + 2000; while (performance.now < then) {}`, this blocks execution for the desired length of time. It's not performance, it's not good practice, but that's not what I was asking! @Bergi, despite your insistence to the contrary, the new link you posted led me to the answer I needed, so thank you! – Derek Henderson Oct 16 '19 at 22:27
  • @DerekHenderson I already mentioned busy-waiting above, but I don't consider it a solution (even if by the book it fulfills the requirement). – Bergi Oct 16 '19 at 22:32
  • But it is precisely what I was looking for, @Bergi! (And precisely why I have been insisting this is not a duplicate of all the "how to handle asynchronous response" questions!) This isn't production code, so I didn't need the answer to "be a good idea" or be performant; I just needed to know if there is a way to do this. – Derek Henderson Oct 16 '19 at 22:38

0 Answers0