Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular data binding won't work with async/await, yet it will with promises

Data bindings don't get updated if their values are changed after an await statement.

  handle() {
    this.message = 'Works'
  }

  async handle() {
    this.message = 'Works'
  }

  async handle() {
    await new Promise((resolve, reject) => {
      resolve()
    })
    this.message = 'Works'
  }

  async handle() {
    await new Promise((resolve, reject) => {
      setTimeout(() => resolve(), 3000)
    })
    this.message = 'Doesn\'t work'
  }

  handle() {
    new Promise((resolve, reject) => {
      setTimeout(() => resolve(), 3000)
    })
    .then(() => this.message = 'Works')
  }

Why do the last two not behave the same? aren't they supposed to be the same thing?

Ionic: 3.9.2

Angular: 5.0.3

TypeScript: 2.4.2

EDIT: I came across another problem with this which may be useful to some.

Changing the values of a binding in the constructor behaves differently to ionViewDidLoad or ngOnInit!

  constructor(private zone: NgZone) {
    // This will cause the same problems, bindings not updating
    this.handle()
  }

  constructor(private zone: NgZone) {
    // Unless you do this...
    this.zone.run(() => {
      this.handle()
    })
  }

  ionViewDidLoad() {
    // But I think this is better/cleaner
    this.handle()
  }
like image 213
ovg Avatar asked Mar 14 '18 17:03

ovg


People also ask

Does async-await only work with promises?

Async/Await is used to work with promises in asynchronous functions. It is basically syntactic sugar for promises. It is just a wrapper to restyle code and make promises easier to read and use. It makes asynchronous code look more like synchronous/procedural code, which is easier to understand.

Is async-await the same as promises?

Await is basically syntactic sugar for Promises. It makes your asynchronous code look more like synchronous/procedural code, which is easier for humans to understand.

Can we use async-await without promise?

This rule applies when the await operator is used on a non-Promise value. await operator pauses the execution of the current async function until the operand Promise is resolved.

Is async-await slower than promises?

I found out that running async-await can be much slower in some scenarios. But if I click on the 'both' button, the 'await' version is ~3-4 times slower than the promises version.


1 Answers

Angular relies on Zone.js for change detection, and Zone.js provides this by patching every API that can provide asynchronous behaviour.

The problem is in how native async functions are implemented. As confirmed in this question, they don't just wrap around global Promise but rely on internal mechanisms that may vary from one browser to another.

Zone.js patches Promise but it's impossible to patch internal promise that is used by async functions in current engine implementations (here is open issue for that).

Usually (async () => {})() instanceof Promise === true. In case of Zone.js, this isn't true; async function returns an instance of native Promise, while Promise global is zone-aware promise patched by Zone.js.

In order to make native async functions work in Angular, change detection should be additionally triggered. This can be done by triggering it explicitly (as another answer already suggests) or by using any zone-aware API. A helper that wraps async function result with zone-aware promise will do the trick:

function nativeAsync(target, method, descriptor) {
  const originalMethod = target[method];
  descriptor.value = function () {
    return Promise.resolve(originalMethod.apply(this, arguments));
  }
}

Here is an example that uses @nativeAsync decorator on async methods to trigger change detection:

  @nativeAsync
  async getFoo() {
    await new Promise(resolve => setTimeout(resolve, 100));
    this.foo = 'foo';
  }

Here is same example that doesn't use additional measures to trigger change detection and expectedly doesn't work as intended.

It makes sense to stick to native implementation in environment that doesn't require transpilation step. Since Angular application is supposed to be compiled any way, the problem can be solved by switching from ES2017 to ES2015 or ES2016 TypeScript target.

like image 157
Estus Flask Avatar answered Oct 17 '22 02:10

Estus Flask