0

I'm working on a project that uses a library called contentRecordHelper. When I run a record object through contentRecordHelper.addTitleFilter(), it populates the record with a bunch of different properties based on what kind of record it was.

Record before:

{
   "_title": "Test Record",
   "_type": "content",
   "_id": "029828ABF"
}

Record after it's run through contentRecordHelper.addTitleFilter():

{
   "_title": "Test Record",
   "_type": "content",
   "_id": "029828ABF",
   "server_id": "VAL8378",
   "client_id": "SIRIUS",
   "$titleFilter":"General",
   "$contentFilter": "Standard:ContentRecord",
   "$contentType":"",
   "$systemType":""
}

As you can see from above, there are two attributes added at the end which are empty: $contentType and $systemType. That's because those two are populated by an asynchronous function which is started within contentRecordHelper.addTitleFilter(), and which gets added approximately 1000ms later.

However, as soon as this data is generated, it is passed into a grid. And that grid must be refreshed if the information is updated, so that it can display properly. Fortunately there is a grid.refreshGrid() function to do so. I have been calling it with a timeout, so my code looks like this:

function addRecordToGrid(record: any): void {
    this.contentRecordHelper.addTitleFilter(record);
    this.grid.rowData.push(record);
    setTimeout(() => {
       this.grid.refreshGrid();
    }, 1000);
}

Now strictly speaking this works fine, but one of my senior devs pointed out to me that we should not rely on a hard 1000 ms, because that won't always be reliable in the case of a slow back-end connection. He said that instead, I should use a promise, so that as soon as record.$systemType && record.$contentType exist, I call grid.refreshGrid(). And he's right - that makes perfect sense. The only trouble is, I've never written a promise before, and just about every example I can find calls some asynchronous function, and waits for it to complete. In my case, contentRecordHelper.addTitleFilter() completes almost instantly, even though certain parts of the result don't come through until later.

How can I write a promise that, simple as heck, just waits for a condition to be true, such as (record.$systemType && record.$contentType) ? If it's not possible, then what is the best way for me to use a promise to solve my issue? Is it possible to do this without making changes to contentRecordHelper.addTitleFilter()? The mechanisms behind that function are very complicated and if I can possibly avoid making changes to it that would save me a lot of stress.

I'm pretty new to the project I'm working on (and typescript in general), so I apologize if this is obvious.

SemperCallide
  • 1,950
  • 6
  • 26
  • 42
  • 2
    Are you sure `contentRecordHelper.addTitleFilter` doesn't take a callback argument, or return a promise? Most asynchronous functions do, and if it doesn't, it should be made to do so. – Michael Hewson Mar 14 '17 at 23:24

3 Answers3

3

As the async code is in contentRecordHelper.addTitleFilter it won't help you much to return a promise, because you don't know when the async operation ends.
Javascript is event driven, there's no way to wait on an object, the only way to know that the operation is done is to add code in contentRecordHelper.addTitleFilter which returns a promise.

What you can do is this:

function addRecordToGrid(record: any): Promise<void> {
    this.contentRecordHelper.addTitleFilter(record);

    return new Promise<void>((resolve, reject) => {
       const check = () => {
            setTimeout(() => {
                if (record.$systemType && record.$contentType) {
                    resolve();
                } else {
                    check();
                }
            }, 1000);
        }

        check();
    });
}

And then use it like so:

this.addRecordToGrid(record).then(() => this.grid.rowData.push(record));

But as you can see, it will just keep looping the timeout until the properties are set, it won't actually know that the operation is over.

Nitzan Tomer
  • 155,636
  • 47
  • 315
  • 299
  • I was torn between awarding this answer as the correct one or the answer by @Bergi. I ultimately chose this one because, as I mentioned in my question, I really can't make changes to contentRecordHelper.addTitleFilter(). I don't have the rights and jumping through the hoops of getting the rights just to make a small change isn't in the cards. The solution that you suggested works great. Thank you! – SemperCallide Mar 15 '17 at 21:12
3

How can I write a promise that, simple as heck, just waits for a condition to be true, such as (record.$systemType && record.$contentType)?

Yes, that's trivially possible. But no, this is not what you should do. You could have done the polling with the plain setTimeout as well (just repeatedly defer the refreshGrid call as long as the data is not available), but this is not what the senior dev meant.

He said that instead, I should use a promise

and meant that contentRecordHelper.addTitleFilter() should return a promise for when it has finished updating the record. You would then write

function addRecordToGrid(record: any): void {
    this.contentRecordHelper.addTitleFilter(record).then(() => {
        this.grid.refreshGrid();
    });
    this.grid.rowData.push(record);
}

Of course, no, that's not possible without changing addTitleFilter, but it should be relatively easy when every asynchronous function returns a promise that you can simply chain onto or return.

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
0

Since the contentRecordHelper.addTitleFilter function is asynchronous, it should have some way of telling you when it is finished. Either it returns a Promise (the right way), takes a callback(the old way), or an event is fired when the title filter is added.

If it doesn't do any of those things, and you aren't able to change it, then you'll have to resort to a hack - such as polling, like in Nitzam's answer.

Michael Hewson
  • 1,444
  • 13
  • 21