1

I am converting some Q-promise based typescript code to ES6-promises.

At a certain point, I used Q.defer and in my migration I just rewritten defer as an ES6 promise like explained in this comment: https://stackoverflow.com/a/49825137/13116953

I was trying to get rid of this defer approach, if possible, and was looking for alternative solutions.

Reasons of my question are:

  1. Deferred promises are considered an anti-pattern, in general
  2. Want to know if this scenario is one of the few where deferred are really the only way

Here's my scenario:

// app start, this is my low level API
init() {
    commService.subscribe("myRecordId", {
        onRecordAdd: this.onAdd
    });
}

...

private _myRecord: MyRecordObj | null = null;
private _recordReceivedDef = newDeferred(); // was Q.defer()

// callback will be called when record is received
private readonly onAdd = (recordObj: MyRecordObj) => {
    this._myRecord = recordObj;
    this._recordReceivedDef.resolve(recordObj);
}

...

// here's my async code that requires the deferred
public readonly doStuff = async () => {
    // ...
    const myRec = await this._recordReceivedDef.promise;
    // use myRef
}

My question is: is there a way I can get rid of this defer? I was thinking of something that resolves when _myRecord changes, but have no idea how to do it.

Side note: I use MobX in other parts of our app, thus having

await when(() => this._myRecord); // of course _myRecord must be @observable

would be handy, but unfortunately I cannot use MobX in this particular piece of code.

Any help is much appreciated.

Thanks a lot!

2 Answers2

1

Assuming init is called before doStuff, the proper way would be

init() {
    this._myRecordPromise = new Promise((resolve, reject) => {
        commService.subscribe("myRecordId", {
            onRecordAdd: (recordObj: MyRecordObj) => {
                // callback will be called when record is received
                resolve(this._myRecord = recordObj);
            }
        });
    });
}

…

private _myRecord: MyRecordObj | null = null;
private _myRecordPromise: Promise<MyRecordObj>;

…

public readonly doStuff = async () => {
    …
    const myRec = await this._myRecordPromise;
    // use myRef
}

You might even drop the _myRecord completely and keep only the _myRecordPromise.

However, you might want to consider not constructing your instance at all before the record is received, see Is it bad practice to have a constructor function return a Promise?.

If init is called at some arbitrary time, you will need some kind of defer pattern, but you don't need newDeferred() for that. Just write

init() {
    commService.subscribe("myRecordId", {
        onRecordAdd: this.onAdd
    });
}

…

private _myRecordPromise: Promise<MyRecordObj> = new Promise(resolve => {
    this.onAdd = resolve;
});
private readonly onAdd: (recordObj: MyRecordObj) => void;
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
0

For people who might be interested, there is also another solution that uses an event emitter approach. Suppose you have a class EventEmitter that implements the following interface:

// pseudo code
type UnsubscribeFn = () => void;
interface IEventEmitter<T> {
    /** Fires an event */
    emit(args: T): void;
    /** Subscribes to emissions of this event and provides an unsubscribe function */
    subscribe((args: T) => void): UnsubscribeFn;
}

you can provide your clients with a whenAdded function like below

// pseudo code
class Subscriber {
    readonly onAddEmitter = new EventEmitter<MyRecordObj>();

    // app start
    init() {
        commService.subscribe("myRecordId", {
            onRecordAdd: this.onAdd
        });
    }
    
    onAdd = (rec: MyRecordObj) => {
           // do some stuff
           onAddEmitter.emit(rec);
    }
    
    whenAdded(): Promise<MyRecordObj> {
        return new Promise((res) => {
            const unsub = onAddEmitter.subscribe((record: MyRecordObj) => {
                unsub();
               res(record); 
            });
        });
    }
}

The goals achieved are:

  • Get rid of Q promises in favour of ES6 ones
  • Adapt the commService event API to a promise-based one
Dharman
  • 30,962
  • 25
  • 85
  • 135