使用FetchJsonP / Redux / React对API调用进行错误处理

时间:2017-10-03 02:14:31

标签: javascript redux redux-observable

嘿我想找出一种方法来处理redux史诗中的错误和api调用,我已经检查了这个文档: https://redux-observable.js.org/docs/recipes/ErrorHandling.html 我没有任何错误,但没有任何反应代码似乎循环

/**
 * Request the JSON guide to the API
 * and then dispatch requestGuideFulfilled & requestGameTask actions
 *
 * @param action$
 */
export function requestGuide(action$) {
  return action$.ofType(REQUEST_GUIDE)
    .mergeMap(({id}) => fetchJsonp(`${API_URL}/guide/${id}/jsonp`)
      .catch(error => requestGuideFailed(error))
    )
    .mergeMap(response => response.json())
    .mergeMap(json => requestGuideFulfilled(json))
    .map(json => requestGameTask(json))
}


export function manageRequestGuideError(action$) {
  return action$.ofType(REQUEST_GUIDE_FAILED)
    .subscribe(({error}) => {
      console.log('Error',error)
    })
}

任何想法?谢谢!

[更新]:即使在抓取时我也有错误:

  

您提供了一个无效的对象,其中包含一个流。您可以   提供Observable,Promise,Array或Iterable。

1 个答案:

答案 0 :(得分:1)

有很多问题,所以我会尽力而为。坦率地说,RxJS 不容易。我鼓励你在使用redux-observable之前花一些时间学习基础知识,除非你当然只是在你的空闲时间进行实验以获得乐趣并且你喜欢痛苦。

除非你真的需要复杂的副作用管理,否则不要引入像redux-observable这样的东西也很关键。有点不幸的是,文档目前只有简单的例子,但是redux-observable真的是为了制作复杂的东西,比如复用的websockets,精心设计的基于时间的排序等,这样做更加容易,而不需要真正了解RxJS。所以我想我说的是,如果你确实需要redux,请确保你也需要redux-observable或者可以使用redux-thunk。看似有意思的是,redux-observable sorta的制造商之一让人们不再使用它,但我只是看到一些疯狂的人使用像redux-observable / redux-saga这样的东西,这些东西根本就不合理。他们带来的复杂性。 最了解你的需求,所以不要把它作为学说或不合理的劝阻<3

此答案中的所有代码均未经过测试,因此可能需要进行少量更正。

  

您提供了一个无效的对象,其中包含一个流。您可以提供Observable,Promise,Array或Iterable。

此错误可能是由.mergeMap(json => requestGuideFulfilled(json))引起的。看起来requestGuideFulfilled是一个动作创建者,但来源并不包括在内,所以我无法确定。 mergeMap又名flatMap希望你返回另一个流(通常是一个Observable),所以一个动作POJO需要包含在一个Observable中,比如Observable.of(requestGuideFulfilled(json)),但在这种情况下使用{{1}是不必要的。它可能只是一个普通的mergeMap

map()

在redux-observable中,所有Epics 必须返回一个Observable。这个Epic返回一个可观察的订阅(export function manageRequestGuideError(action$) { return action$.ofType(REQUEST_GUIDE_FAILED) .subscribe(({error}) => { console.log('Error',error) }) } 的返回值)。这确实会产生错误,但是由于bug in RxJS it's been silently swallowed

相反,您可以使用subscribe()do创建一个Observable来监听该操作,将该属性记录下来,然后忽略它(从不发出任何东西)。所以它只读了#34;基本上

ignoreElements

下一个也是最大的问题是您放置了export function manageRequestGuideError(action$) { return action$.ofType(REQUEST_GUIDE_FAILED) .do(({error}) => { console.log('Error',error) }) .ignoreElements(); } 。了解如何使用RxJS意味着我们将Observables链接在一起非常重要 - &#34;运营商&#34;基本上取一个源并返回另一个Observable,它会懒散地对通过它们传输的数据应用一些操作。这与使用数组的函数式编程(例如catch)非常相似,但有两个主要区别:Observables是惰性的,它们具有时间维度。

运营商如何运作

所以考虑一下这个Observable链:

arr.map().filter()
  • 我们创建一个Observable,当订阅时,将同步发出1,然后是2,然后是3。
  • 我们将Observable.of(1, 2, 3) .map(num => num.toString()) .filter(str => str !== '2'); .subscribe(value => console.log(value)); 运算符应用于该源,该源创建一个新的Observable,当订阅时,它将自己订阅源,我们将其应用于:我们的Observable为1,2,3。
  • 然后,我们应用map运算符filter地图to the Observable returned by过滤器. As you might have guessed,地图`Observable本身已应用于某个来源,它也将订阅其来源,拉动在第一个数字并开始地图 - &gt;过滤操作。

将这些中间Observable存储为变量可能会有所帮助,以便对事物进行一些神秘化。

returns a new Observable that, when subscribed to, will itself subscribe to the source we applied it to: our Observable of strings we mapped. Because that

内部可观测量

当我们使用const source1: Observable<number> = Observable.of(1, 2, 3); const source2: Observable<string> = source1.map(num => num.toString()); const result: Observable<string> = source2.filter(str => str !== '2'); mergeMapswitchMap这样的运算符时,我们说我们想要将每个值映射到另一个&#34;内部&#34;可观察的值将在前一个内部Observable之后合并,切换到或排队(concat)。它们有着不同的重要区别,但如果您不熟悉,它们会有很多资源。

在您的情况下,我们使用concatMap,其别名为mergeMap,这是函数式编程中使用的更为广泛的术语。 flatMap将为您的投影函数提供每个值,并同时订阅您为每个值返回的Observable。每个内部Observable的值都是合并

在函数式编程中,他们更经常称之为扁平化,而不是合并。因此,首先在数组的上下文中考虑这种合并/展平可能会有所帮助

Array.prototype.map

mergeMap

这导致了一组数字[1, 3, 5].map(value => { return [value, value + 1]; }); // [[1, 2], [3, 4], [5, 6]] Array<Array<number>> 的数组,但是如果我们想要一个扁平的数组呢?输入Array<Array<number>>

Array.prototype.flatMap(stage 2 TC39 proposal

flatMap

JavaScript数组尚未正式拥有[1, 3, 5].flatMap(value => { return [value, value + 1]; }); // [1, 2, 3, 4, 5, 6] Array<number> ,但截至撰写本文时,它还是stage 2 TC39 proposal。它遵循与典型flatMap相同的语义:对于数组中的每个项,将其映射到投影函数提供的另一个数组,然后将每个项展平为一个新数组。

使用Observables,它几乎是一样的,除了它们是懒惰的并且有时间维度:

flatMap

我们将每个数字映射到他们自己的两个数字的Observable中。所以一个更高阶的Observable Observable.of(1, 3, 5).map(value => { return Observable.of(value, value + 1); }); // Observable.of(1, 2)..Observable.of(3, 4)..Observable.of(5, 6) | Observable<Observable<number>> ,可能不是我们想要的大多数情况。

Observable<Observable<number>>

现在我们只有一个包含所有数字的流。完美!

错误处理

结合我们对操作链和Observable展平的理解,我们来到错误处理。希望这篇引文能让下一部分更容易理解。

如果在我们的任何一个链式Observable中抛出错误,它将以与值相同的方式传播通过链,但是在它自己的&#34; channel&#34;基本上。因此,如果我们有一个Observable链Observable.of(1, 3, 5).flatMap(value => { return Observable.of(value, value + 1); }); // 1..2..3..4..5..6 | Observable<number> 并且a -> b -> c发生错误,我们会将其发送到a然后b。当每个Observable收到错误时,它可以以某种方式处理它,或者选择将它传递给订阅它的任何东西。如果是这样,该订阅将终止,并且不再侦听来自其来源的未来消息。

大多数操作符只是传递错误(在终止时),所以如果你没有使用像c这样的特殊错误处理操作符,那么错误会传播到你的观察者 - 你自己传递给它的那个catch。如果您提供了错误处理程序,则会调用它,如果没有,则将其作为正常的JavaScript异常重新引发。

终于到达您的代码,让我们从结束开始;我认为你真正想要的是什么:

.subscribe(next, error, complete)

现在让我们分解它。

承诺与可观察

您首先要看到的是,我将function getGuide(id) { const promise = fetchJsonp(`${API_URL}/guide/${id}/jsonp`) .then(res => res.json()); return Observable.from(promise); } export function requestGuide(action$) { return action$.ofType(REQUEST_GUIDE) .mergeMap(({id}) => getGuide(id) .mergeMap(json => Observable.of( requestGuideFulfilled(json), requestGameTask(json) )) .catch(error => Observable.of( requestGuideFailed(error) )) ) } 抽象为fetchJsonp函数。您也可以将此代码放在史诗中,但如果您决定进行测试,将其分开将使您更容易模拟它。

我尽快将Promise包装在一个Observable中。主要是因为如果我们选择使用RxJS,我们应该全押,特别是为了防止以后混淆。例如Promise和Observable实例都有getGuide种方法,因此如果你开始混合这两种方法,很容易引起错误。

理想情况下,我们完全使用Observable而不是Promises,因为Promises无法取消(所以你无法取消实际的AJAX请求+ JSON解析本身),尽管如果你将它包装在Observable中并在promise解析之前取消订阅,Observable将正确地忽略后来解决或拒绝的承诺。

发出多项行动?

它并非100%清晰,但似乎您打算发出两个动作以响应成功取回JSON。您之前的代码实际上将JSON映射到catch操作,但是下一个操作符将该操作映射到requestGuideFulfilled()(它没有收到JSON,它接收requestGameTask()操作)。请记住,关于运算符如何是Observables的链,值会流过它们。

要解决这个问题,我们需要思考思考&#34;在溪流&#34;。我们的requestGuideFulfilled() Observable将发出一个值,即JSON。给定单个(1)值,我们希望将其映射到多个其他值,在这种情况下是两个动作。所以我们想要改变一对多。我们需要使用getGuide()mergeMapswitchMap中的一个。在这种情况下,由于我们的concatMap将永远不会发出多个项目,因此所有这三个运算符都会产生相同的结果,但理解它们至关重要因为确实很重要所以记住这一点!在这种情况下,我们只使用getGuide()

mergeMap

.mergeMap(json => Observable.of( requestGuideFulfilled(json), requestGameTask(json) )) 支持任意数量的参数,并将按顺序发出每个参数。

捕捉错误

因为我们的Observable链是......好......链,他们之间的数据是管道。正如我们上面所了解的那样,因为 这个,所以在这些链中放置错误处理非常重要。错误处理与传统异常甚至承诺之间实际上并没有太大差别,但Promise没有&#34;运营商&#34;,所以人们通常不会遇到这种混乱。

Observable.of运算符是最常见的,它与catch Promise非常相似,除了你必须返回你想要的值的Observable,而不是值本身即可。 catch在这里很常见,因为我们通常只想按顺序发出一个或多个项目。

Observable.of

每当我们应用此运算符的源发出错误时,它将捕获它,而是发出.catch(error => Observable.of( requestGuideFailed(error) )) 然后requestGuideFailed(error)

因为它会针对错误发出操作,因此我们应用于此complete() **结果的所有运算符也可以对我们.catch()发出的值进行操作。

catch

尽管redux-observable不是唯一的(因为redux-observable主要只是一个很小的库和约定,使用RxJS),你经常会看到Epics遵循类似的模式。

  • 收听特定操作
  • 然后将该动作合并或切换到执行副作用的内部Observable
  • 当副作用成功时,我们getJsonSomehow() .catch(error => Observable.of( someErrorAction(error) )) .map(json => { // might be the JSON, but also might be the // someErrorAction() action! return someSuccessAction(); }) 成功行动
  • 如果出现错误,我们会在map / catch内放置mergeMap,但最常见的是在内链的末尾,以便我们发出的任何操作都不会意外转型。

您希望从redux-observable docs中识别出一般模式:

switchMap

将这些知识应用到我们以前的工作中:

function exampleEpic(action$) {
  return action$.ofType(EXAMPLE)
    .mergeMap(action =>
      getExample(action.id)
        .map(resp => exampleSuccess(resp))
        .catch(resp => Observable.of(
          exampleFailure(resp)
        ))
    );
}

关于它,我认为。

唷!对不起,这太啰嗦了。你完全有可能知道其中的一部分或全部,所以请原谅我,如果我在讲唱合唱团的话!我开始写一些简短的内容,但在澄清之后不断加入澄清。洛尔。

如果您正在努力,请确保使用RxJS和redux-observable(或任何复杂的中间件)是您的应用程序的必要复杂性。