0

The result of a PUT operation against a database, is sometimes ok even if it doesn't come back as a HTTP code 200.

In this specific case I want to treat 400 as another okay error, but struggle to come up with an elegant way of doing it.

# stream from a promise any non 200 is fail
putter = Bacon.fromPromise @_exec 'PUT', '/some/resource', {}   
errors = putter.errors().mapError(errBody)    # now errors are normal values
badErrors = errors.filter(statusIsnt(400)).flatMap (body) -> 
  return new Bacon.Error body                 # and now they are errors again
okErrors = errors.filter(statusIs(400)).flatMap (body) -> {}
noError = putter.mapError().filter (v) -> v?  # attempt to get rid of errors
Bacon.mergeAll noError, okErrors, badErrors   # combine to get result stream

I come from a promises background, and I find the above somewhat clumsy, which leads me to conclude I'm missing something. Compare:

@_exec 'PUT', '/some/resource', {}
.fail (err) ->
  if err.body.status == 400 # ok
    return {}
  else
    throw err
Martin Algesten
  • 13,052
  • 4
  • 54
  • 77
  • I think the "best" answer will depend on what you wish to do with the result value. You could also look at EventStream.subscribe, which receives all events, including errors. Further, I don't think your comparison is fair, since if all you wanted to do was throw non-400 errors, that would be a simple use of onError. – chreekat Dec 28 '14 at 09:53
  • 1
    Fair point. This is init code. I want to do stuff depending on whether it succeeds or fails. The promise I could at this point just chain on a `.then` to go on after init, same with `.onValue` from the `mergeAll`. Both 200 answers (which ends up in `noError`) and these "okay" errors should give me a point to continue after init. – Martin Algesten Dec 28 '14 at 10:43

3 Answers3

1

You can use withHandler with Bacon.Error for throw and (implicit) Bacon.Next for result. But the code will look very similar to your handling already in promise. Actually promise version has less boilerplate.

Compare your promise version

promise = @_exec('PUT', '/some/resource', {})
recovered = promise.fail (err) ->
  # 400 is ok. make it ok already in promise
  if err?.body?.status == 400 then {} else throw err.body
result = Bacon.fromPromise recovered

and withHandler Bacon "only" version

promise = @_exec('PUT', '/some/resource', {})
stream = Bacon.fromPromise promise
result = stream.withHandler (event) ->
  if (event.hasValue())
    @push(event)
  else
    # error
    if event.error?.body?.status == 400 then
      @push({})
    else
      @push(event)
phadej
  • 11,947
  • 41
  • 78
0

One possible solution for this specific scenario is to amend the result already in the promise, but I can easily envisage cases where you can't do that.

Bacon.fromPromise @_exec('PUT', '/some/resource', {}).fail (err) ->
    # 400 is ok. make it ok already in promise
    if err?.body?.status == 400 then {} else throw err.body

Surely there must be a better functional approach to this problem?

Martin Algesten
  • 13,052
  • 4
  • 54
  • 77
0

withHandler is an underlying method that lets you operate on Events of all types. Within it you can call @push, which will send errors to the error stream and values to the value streeam. Perhaps the following:

Bacon.fromPromise(@_exec('PUT', '/some/resource', {}))
    .withHandler((event) ->
        if event.isError && event.error.body?.status? == 400
            @push(new Bacon.Next({}))
        else
            @push(event)
    )
chreekat
  • 974
  • 7
  • 16
  • This looks almost exactly what I want, but it doesn't work as expected. The returned Bacon.Error goes into the *value* stream. `Bacon.once(new Bacon.Error('fail')).mapError((e) -> new Bacon.Error(e)).subscribe (e) -> console.log e` # as expected: fail, `Bacon.once(new Bacon.Error('fail')).mapError((e) -> new Bacon.Error(e)).onError (e) -> console.log e` # unexpected: /nothing/ `Bacon.once(new Bacon.Error('fail')).mapError((e) -> new Bacon.Error(e)).onValue (e) -> console.log e` # unexpected: fail – Martin Algesten Dec 29 '14 at 05:20
  • Oh, I understand. It looks like Oleg is right -- you want withHandler, although I think there could be a simpler implementation. I'll amend my answer. – chreekat Dec 30 '14 at 12:34