0

In my Angular 4 component I have a sort method. It's really just as simple as clicking the header of a table and changing the sort order by that column, and ascending/descending. However, there is a call to a service in there which could eventually become quite expensive, so I'm trying to make this call asynchronous. Example:

sortColumn(): void {
    this.service.someExpensiveBlockingOperation();
    this.sortOrder = !this.sortOrder;
    this.data.sort((a, b) => { ... });
}

I've tried messing with Promises and Observables, as well as Async / Await but without success. I feel like I'm missing something as JS is supposedly a non-blocking, asynchronous driven language.

elliotwesoff
  • 188
  • 2
  • 11
  • 2
    What is the "anonymous asynchronous function" you are talking about. How is that related to the question? What do you think you might gain from an async call? Is it that `this.data.sort()` shouldn't wait for `this.service.someExpensiveBlockingOperation()` to complete? – Günter Zöchbauer Sep 08 '17 at 08:23
  • Exactly. I want `someExpensiveBlockingOperation()` to start, and then immediately move on to the next lines of code. The expensive operation is basically a back-end call that the user doesn't need to know about, and shouldn't have to wait for that call to finish for the view to be updated. – elliotwesoff Sep 08 '17 at 08:32

2 Answers2

4

as JS is supposedly a non-blocking, asynchronous driven language

No, it isn't. But we commonly use it in environments that lean toward asynchronicity. JavaScript is fundamentally just like any other language, code progresses synchronously step by step. The only asynchronous thing about JavaScript itself, as opposed to the environments in which we run it, is promises and the syntax around them such as async/await.

If you want to make your sort call asynchronous in relation to the trigger, you were right to look at promises:

sortColumn(): void {
    this.sortOrder = !this.sortOrder;
    Promise.resolve().then(() => {
        this.service.someExpensiveBlockingOperation();
        this.data.sort((a, b) => { ... });
    });
}

There I've put the flag flip in the synchronous code, and everything else in the asynchronous callback. The then callback is guaranteed to be called asynchronously.

You could also use setTimeout

sortColumn(): void {
    this.sortOrder = !this.sortOrder;
    setTimeout(() => {
        this.service.someExpensiveBlockingOperation();
        this.data.sort((a, b) => { ... });
    }, 0);
}

In an ES2015+-compliant environment, the then callback would be called sooner than the setTimeout callback, because promise callbacks are "microtasks" run immediately after the macrotask where they were scheduled was completed, whereas a setTimeout callback is a macrotask. In practice, it's rarely relevant (but is, sometimes).

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 1
    Thanks! The `setTimeout()` was exactly what I was looking for. For whatever reason, the Promise would still delay the updating of the view. I'd prefer to use a Promise if I can get it to work. Any ideas? Here's what I have now: `sort(): void { this.data.sort(...); setTimeout(() => expensiveOperation(), 0) }` – elliotwesoff Sep 08 '17 at 08:46
  • 1
    @elli0t: Ah, yes, the update of the view is probably handled in the next macrotask, so this is one of those situations where the microtask vs. macrotask thing matters. I don't think you *can* use a promise if you need to wait for the next macrotask. – T.J. Crowder Sep 08 '17 at 08:49
  • You wouldn't happen to have any reading materials on micro/macrotasks would you? I've never heard of them. – elliotwesoff Sep 08 '17 at 08:52
  • @elli0t: The terminology is somewhat informal and derived from the [HTML spec](https://www.w3.org/TR/html5/webappapis.html#event-loops) (which talks about tasks and microtasks). The [JavaScript spec](http://www.ecma-international.org/ecma-262/8.0/index.html#sec-jobs-and-job-queues) talks of "jobs" and "job queues" (job = task) and clearly separates promise resolution jobs from other jobs, but doesn't talk about doing those between main jobs. *(cont'd)* – T.J. Crowder Sep 08 '17 at 13:03
  • *(continuing)* The short version is that there's a main event loop on which tasks (macrotasks) are scheduled. Microtasks queued during a task are performed immediately after that task completes, *before* the next macrotask in the queue, even if the macrotask was queued first. Implementations seem to be heading to consensus about what jobs are "macro" vs "micro." [This question's answers](https://stackoverflow.com/questions/25915634/) talks more about it. See also my answers [here](https://stackoverflow.com/a/43592450/157247) and [here](https://stackoverflow.com/a/39139643/157247). HTH! – T.J. Crowder Sep 08 '17 at 13:06
0

If you want it asynchronous, simply do so

sortColumn(): void {
    this.service.someExpensiveBlockingOperation().subscribe(result => {
        // Do what you want here 
    });
    this.sortOrder = !this.sortOrder;
    this.data.sort((a, b) => { ... });
}
  • I want the opposite. I don't want it to wait for the service call to complete. The service call should start, and then immediately move on to the actual sort operation. – elliotwesoff Sep 08 '17 at 08:33
  • I had already tried something like that and it didn't work like I wanted it to. Thanks for the reply though. – elliotwesoff Sep 08 '17 at 09:07