-1

Even though javascript runs in single thread, concurency issues may still arise in async functions. Some of them may be avoided by greatly increasing the complexity of the code, but some I solve like this:

// private "lock"
let _lock = null;
// this function waits till the last call is done, then 
// initiates next one
async function doTheStuff() {
    while (_lock) {
        await _lock;
    }
    _lock = actuallyDoTheStuff();
    const result = await _lock;
    _lock = null;
    return result;
}
async function actuallyDoTheStuff() {
    // this function really does the stuff
}

This ensures that only one instance of actuallyDoTheStuff is running, but it doesn't really look that nice.

Will this truly work? Can I be sure there will be no endless loop/lock?

And, whether it works or not, isn't there a better way to do this?

Tomáš Zato
  • 50,171
  • 52
  • 268
  • 778
  • 4
    What exactly is your question? Whether this approach is working? Or whether there are better alternatives? – Bergi Jul 14 '19 at 21:32
  • @Bergi Why not both? If it's not working then I'm obviously looking for a better alternative, since I need code that works. If it **is** working, then I'm obviously looking for a better alternative, since I already have a working code. – Tomáš Zato Jul 14 '19 at 21:57
  • You are basically trying to rewrite Promise.prototype.tben using async/await... – le3th4x0rbot Jul 14 '19 at 22:02
  • Is the real problem to sequence successive calls to a function? Because I can think of much nicer ways of doing that. – jfriend00 Jul 15 '19 at 00:06
  • @jfriend00 Yes, that is the real problem, I need them to happen in the order they were called ideally, to ensure the same resource is not used in parallel. Could you please share your approach? – Tomáš Zato Jul 15 '19 at 09:28

2 Answers2

2

I'd encapsulate everything inside actuallyDoTheStuff, which simply calls .then on the last Promise it generated:

const actuallyDoTheStuff = (() => {
  let lastProm = Promise.resolve();
  return () => {
    const nextProm = lastProm.then(() => {
      return new Promise(resolve => setTimeout(() => {
        console.log('resolving');
        resolve();
      }, 1000));
    });
    lastProm = nextProm;
    return lastProm;
  };
})();

console.log('start');
actuallyDoTheStuff();
actuallyDoTheStuff();
actuallyDoTheStuff();
setTimeout(() => {
  actuallyDoTheStuff();
  actuallyDoTheStuff();
}, 200);

If it may throw, then add a catch when reassigning to lastProm

const actuallyDoTheStuff = (() => {
  let lastProm = Promise.resolve();
  return () => {
    const nextProm = lastProm.then(() => {
      return new Promise(resolve => setTimeout(() => {
        console.log('resolving');
        resolve();
      }, 1000));
    });
    lastProm = nextProm.catch(() => null);
    return nextProm;
  };
})();

console.log('start');
actuallyDoTheStuff();
actuallyDoTheStuff();
actuallyDoTheStuff();
setTimeout(() => {
  actuallyDoTheStuff();
  actuallyDoTheStuff();
}, 200);
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • Thanks, this looks better, since it enforces particular order of the calls. But I don't understand: How come the `lastProm` variable somehow persists between calls? How come this works without global variable? – Tomáš Zato Jul 14 '19 at 22:01
  • 1
    The variable `lastProm` is created in the IIFE in which the inner `actuallyDoTheStuff` is defined. The IIFE runs *immediately*, creates `lastProm` in that scope (visible to the inner arrow function, and nothing else), and then the IIFE returns the function which can be called from the outside. See https://stackoverflow.com/questions/8228281/what-is-the-function-construct-in-javascript – CertainPerformance Jul 14 '19 at 22:03
  • Oh yeah, I missed a couple of parentheses there. – Tomáš Zato Jul 14 '19 at 22:21
0

I'm not sure exactly what actuallyDoTheStuff eventually should do, but if you're trying to sequence multiple calls of it (and await each call), you could make doTheStuff an async wrapper function with a for loop that awaits actuallyDoTheStuff on each iteration:

function actuallyDoTheStuff( iteration ) {
  console.log( "Waiting...")
  return new Promise( res => {
    setTimeout( () => {
      res( iteration );
    }, 150 );
  } );
}

async function doTheStuff() {
  for ( let i = 0; i <= 5; i++ ) {
    const result = await actuallyDoTheStuff( i );
    console.log( result );
  }
}

doTheStuff();

Or alternatively make actuallyDoTheStuff a recursive function:

let index = 1;
async function actuallyDoTheStuff( i ) {
  if ( i <= 5 ) {
    console.log( "Waiting..." )
    await new Promise( res => {
      setTimeout( () => {
        console.log( i );
        i++
        res();
        actuallyDoTheStuff( i );
      }, 150 );
    } );
  }
}
actuallyDoTheStuff( index );
Yannick K
  • 4,887
  • 3
  • 11
  • 21