1

I have multiple functions reading from/writing to the same file. The functions are called after certain HTTP requests. Thus, to avoid conflicts when accessing the same file simultaneously, I build some kind of promise chain:

class PromiseChain {
  private current: Promise<any> = Promise.resolve();

  public push<T, TResult1 = T, TResult2 = never>(
    onfulfilled?:
      | ((value: T) => TResult1 | PromiseLike<TResult1>)
      | undefined
      | null,
    onrejected?:
      | ((reason: any) => TResult2 | PromiseLike<TResult2>)
      | undefined
      | null
  ): Promise<TResult1 | TResult2> {
    return (this.current = this.current.then(onfulfilled).catch(onrejected));
  }
}

which can be used like so:

const chain = new PromiseChain();
const defer = (message: string, timeout: number) => () =>
  new Promise((resolve) => setTimeout(() => resolve(message), timeout)).then(
    console.log
  );

chain.push(defer('1st', 1000), (error) => console.log(error.message));
chain.push(defer('2nd', 500));

Thus, the promises are being executed in the same order they have been pushed to the chain (1st then 2nd). Errors along the chain can be caught, so the chain will not break.

Is there any built-in or an easier way to achieve this? I was thinking along the lines of rxjs Subjects or the like.

DonFuchs
  • 371
  • 2
  • 14
  • "*errors along the chain can be caught*" - but you're not actually doing it! – Bergi Aug 03 '23 at 13:23
  • "*Is there any built-in or an easier way to achieve this?*" - the example you've given can be trivially simplified not use a class instance, just write `Promise.resolve().then(defer('1st', 1000)).then(defer('2nd', 500));`. So what do you actually need? – Bergi Aug 03 '23 at 13:25
  • @Bergi The calls to `push` come from different functions at different times (as I've already explained), so it's not only one line. – DonFuchs Aug 03 '23 at 14:01
  • @Bergi I've added an error handler to my example code, but that should not make a huge difference. – DonFuchs Aug 03 '23 at 14:02
  • The example caller in the OP says: `const chain = new PromiseChain();`. That will produce a new chain. Unless another caller hangs the next promise on the very same `chain`, the promises will resolve independently. Make the promise chain class a singleton, or use a global version of `current`, which is about the same thing – danh Aug 03 '23 at 14:29
  • @danh Well that's what I'm doing. Even in my simplified example both calls of `push` are on the same `chain`. I don't get the point – DonFuchs Aug 03 '23 at 14:43
  • ok so you `push` two `defer` on the `chain` and they will run in sequence.. can you explain what happens next? guessing something like `chain.current.then(result => ...)`? what is the type of `result`? – Mulan Aug 03 '23 at 14:45
  • 2
    it looks like you're reinventing `.then` – Mulan Aug 03 '23 at 14:57
  • @DonFuchs, but it seems like you're getting expected results. Begin a longer running promise, `then` a quicker running promise. The quicker one will start *after* the longer one resolves. – danh Aug 03 '23 at 15:15
  • "*The calls to `push` come from different functions at different times*" - then what you did is fine. No, there is no builtin that makes it easier - but I don't see how the `push` implementation could get any simpler – Bergi Aug 03 '23 at 16:26
  • @Bergi Yes, this already pretty simple, and it meets all my requirements. I was just wondering if maybe my overall approach may be wrong. For such a basic problem it just does not feel perfectly right, I've never seen sth like this. Also, I thought rxjs might have something built-in in store. – DonFuchs Aug 04 '23 at 06:43
  • @DonFuchs With rxjs you'd probably use a stream of reads and writes per file, emitted from across the application(?), and then `concatAll` or `concatMap` them – Bergi Aug 04 '23 at 10:52
  • @Bergi That's what I was thinking of... could you maybe elaborate on that, I'm not very familiar with streams. – DonFuchs Aug 04 '23 at 13:47
  • @DonFuchs If you're not already familiar with rxjs and not already using it, then your `PromiseChain` will definitely be simpler, faster, and easier to understand – Bergi Aug 04 '23 at 13:49
  • @Bergi rxjs is fine, only file streams I don't know very well. If I knew how to replace `readFile`/`writeFile` with file stream operations I would manage the rxjs bit myself. If I had for example a global stream, keeping the file open all the time and piping in data from different functions one by one, maybe that would be a better approach?! – DonFuchs Aug 04 '23 at 15:33
  • 1
    @DonFuchs I meant that every action you'd do on a file would be an item ("event"?) in the stream. You could then of course optimise your file operations globally in various ways with stream operators. Whether that's a "better" or even "suitable" approach depends on what exactly you need, which you probably should ask as a new question. – Bergi Aug 04 '23 at 15:43
  • @Bergi Ok, thanks. I posted another question here: https://stackoverflow.com/questions/76841438/fs-best-way-of-mutual-asynchronous-read-write-to-single-file – DonFuchs Aug 05 '23 at 11:34

1 Answers1

-1

What you're looking for is the concat operator:

import { of, delay, concat } from 'rxjs';

const mockValues = [
  of(`1st`).pipe(delay(1000)),
  of(`2nd`).pipe(delay(1000)),
  of(`3rd`).pipe(delay(1000)),
];

concat(...mockValues).subscribe(console.log);

Live demo

maxime1992
  • 22,502
  • 10
  • 80
  • 121