RxJS,理解 defer

35

我搜索了RxJS中defer的用法,但仍然不明白何时以及为什么要使用它。

据我所知,只有当有人订阅它之前,Observable方法才会被触发。

如果是这样的话,那么为什么我们需要用defer来包装一个Observable方法呢?

示例链接

我仍然想知道为什么要使用defer来包装Observable?这会有什么不同吗?

var source = Rx.Observable.defer(function () {
  return Rx.Observable.return(42);
});

var subscription = source.subscribe(
  function (x) { console.log('Next: ' + x); },
  function (err) { console.log('Error: ' + err); },
  function () { console.log('Completed'); } );

这篇文章有一些很好的例子,包括使用 Promises 和 async/await 与 RxJS Observable 进行互操作的能力,还可以创建可重试的 Promise。 - Simon_Weaver
6个回答

69

简单来说,因为 Observables 可以封装许多不同类型的源,并且这些源不一定要遵守该接口。有些像 Promises 总是试图急切地完成。

考虑:

var promise = $.get('https://www.google.com');

在这种情况下,承诺已经在连接任何处理程序之前执行。如果我们想让它更像一个Observable,那么我们需要一些方式来延迟承诺的创建,直到有订阅为止。

因此,我们使用defer创建一个只有在订阅结果的Observable时才会执行的块。

Observable.defer(() => $.get('https://www.google.com'));

上面的代码不会创建 Promise 直到 Observable 被订阅,因此它更符合标准的 Observable 接口。


@AlexPineda 不太清楚你的意思。defer 已经理解 Promises,因此它已经隐式处理了它们,所以除非你希望明确使用 fromPromise,否则不需要它。 - paulpdaniels
9
他的意思是,如果你执行 fromPromise(promise),传递给 fromPromisepromise 已经在执行了,fromPromise 只是将该 promise 以 Observable 包装器的形式返回。使用 defer,直到我们订阅时才会启动该 promise。 - Ole

23

如果我们考虑使用日期,那么理解起来会更容易。

const s1 = of(new Date()); //will capture current date time
const s2 = defer(() => of(new Date())); //will capture date time at the moment of subscription

对于两个可观察对象 (s1 和 s2),我们都需要进行订阅。但当 s1 被订阅时,它会给出常量设置的日期和时间。而 s2 则会给出订阅的日期和时间。

上述代码摘自 https://www.learnrxjs.io/operators/creation/defer.html


19

以(这篇文章)为例:

const source = Observable.defer(() => Observable.of(
  Math.floor(Math.random() * 100)
));

为什么不直接将source Observable设置为of(Math.floor(Math.random() * 100)呢?

因为如果我们这样做的话,表达式Math.floor(Math.random() * 100)会立即运行并作为一个值在我们订阅source之前就可用了。

我们想要延迟表达式的求值,所以我们使用defer来包装of。现在,表达式Math.floor(Math.random() * 100)将在订阅source时被求值,而不是在此之前任何时候。

我们正在使用defer工厂函数来包装of(...),使得当订阅source observable时才构建of(...)


18
值得指出的是,由defer()返回的Observable每次被订阅时都会运行提供的工厂函数。因此,当仅使用of(Math.floor(Math.random() * 100))时,每个订阅者将收到相同的随机数,该数在一开始被计算一次。但当将其包装在defer()中时,每个订阅者都将收到一个不同的随机数,在订阅时计算。 - Mike Furtak
或者 const source = defer(async () => Math.floor(Math.random() * 100)) - Marinos An

4

举个例子,假设你想向服务器发送一个请求。你有两种选择。

通过 XmlHttpRequest

如果你没有对现有的ObservableObservable.create(fn)进行订阅,就不会有任何网络请求。只有在你subscribe时才发送请求。这是 Observables 的正常行为,也是它的主要优点。

通过 Promise(fetch、rx.fromPromise)

当你使用 Promises 时,情况就不是这样了。无论你是否已经订阅,它都会立即发送网络请求。要解决这个问题,你需要在defer(fn)中包装 Promises。


2
假设你想创建一个可观察对象,当订阅时,它会执行一个ajax请求。

如果您尝试下面的代码,ajax请求将立即执行,并且在5秒后响应对象将被打印出来,这不是您想要的。

const obs = from(fetch('http://jsonplaceholder.typicode.com/todos/1')); 
setTimeout(()=>obs.subscribe((resp)=>console.log(resp)), 5000)

一种解决方案是手动创建一个Observable,如下所示。
在这种情况下,ajax响应将在5秒后执行(当调用subscribe()时):

let obs = new Observable(observer => {
    from(fetch('http://jsonplaceholder.typicode.com/todos/1')).subscribe(observer)
});
setTimeout(()=>obs.subscribe((resp)=>console.log(resp)), 5000)
< p > defer 可以更直接地实现上述操作,而且不需要使用 from() 将 promise 转换为 observable:

const obs = defer(()=>fetch('http://jsonplaceholder.typicode.com/todos/1'))
setTimeout(()=>obs.subscribe((resp)=>console.log(resp)), 5000)

2
实际上,您可以完全用常规函数替换defer。但是,在订阅之前,您必须调用该函数。
function createObservable() {
    return from(fetch('https://...'));
}

createObservable().subscribe(...);

在使用defer时,你只需要将createObservable函数传递给defer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接