Búsqueda de sitios web

javascript - La vinculación de datos en Angular no funciona con async/await, pero sí funciona con promesas


Los enlaces de datos no se actualizan si sus valores cambian después de una sentencia await.

  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')
  }

¿Por qué los dos últimos no se comportan igual? ¿No se supone que son la misma cosa?

Iónico: 3.9.2

Angular: 5.0.3

TypeScript: 2.4.2

EDIT: Me encontré con otro problema con esto que puede ser útil para algunos.

¡Cambiar los valores de un binding en el constructor se comporta de manera diferente a ionViewDidLoad o 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()
  }

Respuesta #1

Angular depende de Zone.js para la detección de cambios, y Zone.js proporciona esto parcheando cada API que puede proporcionar comportamiento asincrónico.

El problema está en cómo se implementan las funciones async nativas. Como se confirma en esta pregunta, no solo se envuelven alrededor del Promise global, sino que dependen de mecanismos internos que pueden variar de un navegador a otro.

Zone.js parchea Promise, pero es imposible parchear la promesa interna que es utilizada por las funciones async en las implementaciones actuales del motor (aquí hay un problema abierto al respecto).

Normalmente (async() => {})() instanceof Promise === true. En el caso de Zone.js, esto no es cierto; la función async devuelve una instancia de Promise nativa, mientras que el Promise global es una promesa consciente de zona parcheada por Zone.js.

Para que las funciones nativas async funcionen en Angular, se debe activar adicionalmente la detección de cambios. Esto se puede hacer activándola explícitamente (como ya sugiere otra respuesta) o utilizando cualquier API consciente de zonas. Un ayudante que envuelva el resultado de la función async con una promesa consciente de zonas hará el truco:

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

Aquí hay un ejemplo que utiliza el decorador @nativeAsync en métodos async para activar la detección de cambios:

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

Aquí hay el mismo ejemplo que no utiliza medidas adicionales para activar la detección de cambios y, como era de esperar, no funciona como se pretende.

Tiene sentido ceñirse a la implementación nativa en un entorno que no requiere un paso de transpilación. Dado que se supone que la aplicación Angular debe ser compilada de todos modos, el problema se puede resolver cambiando de ES2017 a ES2015 o ES2016 en el target de TypeScript.

Respuesta #2

Tal como dijo estus, actualmente zone.js no soporta async/await nativo, así que no puedes compilar TypeScript que tenga como objetivo ES2017, y estoy trabajando en ello, https://github.com/angular/zone.js/pull/795, he hecho una demostración funcional que puede ejecutarse en nodejs, pero en el navegador (Chrome), todavía tomará algo de tiempo porque Chrome no ha habilitado la versión en JavaScript de AsyncHooks y PromiseHooks por ahora.

Artículos relacionados