【问题标题】:Observables vs Promises - processing, then returning an async resultObservables vs Promises - 处理,然后返回异步结果
【发布时间】:2019-06-12 10:37:17
【问题描述】:

我在 AngularJS 中处理 Promise 方面有相当多的经验,现在我正在尝试了解 Angular 中的 Observables。

喜欢承诺处理单个异步结果 - 它们似乎非常适合这个用例 - 但 Angular 似乎热衷于将 Observables 用于所有事情,所以我现在试图最好地理解使用此模式的实践。

以下是我如何使用承诺链实现某些目标的示例。目标是:

  1. MyClass.value 应该总是doSomethingAsync() 为时设置 叫
  2. 调用者 - 在这种情况下 doSomethingElse() 应该能够链接到 doSomethingAsync()如果它想并且等到它完成再使用结果

export class MyClass {
    private value;

    public doSomethingAsync() {
        return someAsyncCall()
            .then(value => {
                this.value = value
            });
    }
}

export class MyOtherClass {
    public doSomethingElse() {
        const myObj = new MyClass();
        let myValue;

        myObj
            .doSomethingAsync()
            .then(value => {
                myValue = value
            });
    }
}

我正在尝试弄清楚如何使用 Observables 来做到这一点。

我可以在doSomethingAsync() 中使用.pipe()tap() 来捕获该值,但问题是除非doSomethingElse() 在返回的Observable 上调用subscribe(),否则这不会被执行。我不想让doSomethingAsync() 依赖于调用者接下来会做什么。

可以doSomethingAsync() 中调用subscribe() 来捕获该值,但之后我不再有可返回的Observable。所以我想我可能不得不这样做:

export class MyClass {
    private value;
    private valueSubject: Subject<any> = new Subject<any>();

    public doSomethingAsync() {
        someAsyncCall()
            .subscribe(value => {
                this.value = value;
                this.valueSubject.next(value);
            });

        return valueSubject.asObservable();
    }
}

export class MyOtherClass {
    public doSomethingElse() {
        const myObj = new MyClass();
        let myValue;

        myObj
            .doSomethingAsync()
            .subscribe(value => {
                myValue = value
            });
    }
}

但这似乎过于复杂,因为我现在也必须处理 Subjects 并且有效地跟踪 2 个不同的流程。

任何人都可以提出更好的方法吗?在我看来,这似乎更容易处理承诺......?

【问题讨论】:

  • I don't want to make doSomethingAsync() dependent on what the caller does next.正是 observables 的重点,如果你仔细想想,它实际上很有意义。如果对值不感兴趣(即没有订阅者),那么MyClass 为什么要存储任何东西?
  • doSomethingAsync() 方法无法返回值,因为其 .then 块中没有 return 语句。
  • @Adam 我猜使用 promise 模式,一个方法可以同时满足 2 个范式:1)“就做吧!”和 2) “做完之后告诉我”。使用 Observables 这不再可能 - 一个方法只能真正满足一个或另一个。可能我只需要考虑一下,但现在感觉好像我失去了一些有用的东西......
  • 另外 - 我有点恼火这个问题被否决了!我认为我在清楚地提出问题方面做得很好。我是否完全了解 Observable 模式/哲学是另一个问题,但这部分是问题所在。如果有人能请教我,我将不胜感激......?
  • @DanKing - 我是 Promise 的早期采用者,我觉得我很早就开始摸索它们了,但是我对 observables 的游戏迟到了,我花了一段时间才弄清楚它们的价值高于承诺,甚至它们如何真正发挥作用(其中有很多,主要的——我觉得——是我在第一条评论中指出的)。去看看他们吧。你必须考虑的其他一点是,Observables 比 Promise 更手动,你必须订阅它们(与 Promise 相同),但你也有责任在完成后取消订阅。

标签: angular promise rxjs


【解决方案1】:

正如您所说,他们非常热衷于使用 rxjs。对于您只想调用一次的经典后端,解决一个承诺,就是这样,将可观察对象转换为一个承诺并根据您的设计要求返回您的承诺;要实现这样的事情,请在 Observable 上使用来自 rxjs 的 toPromise()

保持 Observable 的想法是,您不断观察一个端点,该端点在数据发生变化时不断返回值,并且您在运行中收到它们,并且每当您完成时,您取消订阅(离开视图或类似的东西) .例如一些使用 websockets 的 api 或一些实时后端,如 Firebase。但这对于调用端点的经典后端没有意义 -> 你得到一个结果 -> 就是这样。因为最后,每次你想得到那里的东西时,你都必须打一个新的电话。

那么您的示例将如下所示:

export class MyClass {
    private value;

    public doSomethingAsync() {
        return someAsyncCall().toPromise();
    }
}

export class MyOtherClass {
    public doSomethingElse() {
        const myObj = new MyClass();
        let myValue;

        myObj
            .doSomethingAsync()
            .then(value => {
                myValue = value
            });
    }
}

【讨论】:

  • 是的,这是一个选项,但是doSomethingElse() 必须处理一个promise 而不是Observable,这意味着我的应用程序现在必须使用这两种模式。另一种选择是使用pipe()tap(),正如我最初建议的那样,但在最后调用toPromise() 以创建隐式订阅,然后使用fromPromise() 将其转换回Observable。不过,这似乎是一个相当不愉快的组合……
  • 您似乎在争辩说,当您只检索一个结果时,Observables 并没有真正意义,我倾向于同意这一点,但似乎与 Angular 采用的模式完全相反...
  • 我刚刚在答案中包含了代码的外观。我可以看到这根本不会乱七八糟,看起来是正确的,并且框架将为“现代”实时 api 做好充分准备。在那里我会明白为什么 Angular 会这样做
  • 通过toPromise() 将 Observable 转换为 Promise 似乎被广泛认为是一种反模式。 Angular 已选择沿 Observable 路由进行 HTTP 调用等,因此这种方法通过将 Promise 模式重新引入到您的应用程序中而与此背道而驰。我猜 Observables do 给你的一件事是更容易retry 能力...
  • 嗯 - 除了doSomethingElse() 在我的示例中无论如何都不能retry(),因为doSomethingAsync() 已经破坏了原来的 Observable 序列并开始了一个新的序列......
【解决方案2】:

好的,我现在已经想出了如何做到这一点。然而,我现在得出结论(见下文),这并不是使用 RxJS 的真正好方法......

export class MyClass {
    private value;

    public doSomethingAsync() {
        const observable = someAsyncCall()
            .pipe(
                tap(value => {
                    this.value = value;
                }),
                share()
            );

        observable.subscribe();

        return observable;
    }
}

export class MyOtherClass {
    public doSomethingElse() {
        const myObj = new MyClass();
        let myValue;

        myObj
            .doSomethingAsync()
            .subscribe(value => {
                myValue = value
            });
    }
}

工作原理:

  1. 订阅 doSomethingAsync() 表示始终触发异步源操作。
  2. 使用share() 意味着调用者也可以订阅而无需重新触发源。 share() 在后台使用主题工厂,这也意味着调用者可以选择重试整个序列。
  3. doSomethingAsync() 中使用tap()(而不是subscribe())来捕获该值,这意味着如果调用者重试,它将被重新捕获 - 这使所有状态保持一致。

好处:

  1. 如果我们对结果不直接感兴趣,则不必订阅 doSomethingAsync() - 无论如何它都会执行操作。
  2. MyClass 负责维护自己的状态 - MyClass 的状态不受调用者是否执行 .subscribe() 函数的影响。

缺点:

  1. 这是非典型行为 - 观察者通常必须在他们做任何事情之前订阅,所以这有点打破了这种期望。
  2. 如果调用者重试订阅,MyClass 的状态仍然会受到影响 - 这是为了保持状态的一致性而设计的,但这确实意味着我没有完全 em> 保留了封装。

结论:

我现在得出的结论是,执行此操作的“正确”方法——不违背我认为 Observables 应该工作的方式——实际上是依靠调用者来执行订阅,即.:

export class MyClass {
    private value;

    public doSomethingAsync() {
        return someAsyncCall()
            .pipe(
                tap(value => {
                    this.value = value;
                })
            );
    }
}

export class MyOtherClass {
    public doSomethingElse() {
        const myObj = new MyClass();
        let myValue;

        myObj
            .doSomethingAsync()
            .subscribe(value => {
                myValue = value
            });
    }
}

好处:

  1. 与以前的解决方案相比,它的代码更少。
  2. 由于doSomethingAsync() 方法返回一个Observable,我认为通常的期望是在你调用.subscribe() 之前什么都不会发生。
  3. 调用者可以重试。
  4. 我认为在这种情况下,返回的 Observable 可以合法地被视为 MyClass 的“朋友”,因此能够修改其状态(通过 .subscribe() 方法)。

缺点:

  1. 仍然比承诺的等价代码略多。
  2. 在概念上比等价的承诺更难理解:
    • 需要了解pipe()tap()subscribe()
    • 至少需要对异步流处理有基本的了解。
    • 对于 Promise,您只需要了解如何使用 promise.then() 将单个异步调用链接在一起,在我看来这是一个更简单的概念。
    • RxJS 的大多数(如果不是全部) 处理功能都是多余的 - 并且只会增加复杂性 - 如果您实际上不处理事件流.
  3. 调用者必须调用 subscribe() 才能使某些事情发生,无论他们是否关心异步操作的结果(或完成)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-09-01
    • 2018-12-16
    • 1970-01-01
    • 2014-10-02
    • 2020-02-10
    • 2014-01-01
    • 1970-01-01
    • 2017-10-19
    相关资源
    最近更新 更多