question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

uncaught error from Promise.reject

See original GitHub issue

A demo here.

Code related below, the race code mostly comes from https://github.com/yelouafi/redux-saga/issues/183:

import { fork, call, put, race, take } from 'redux-saga/effects'
function * api () {
  try {
    let result = yield * callWithLoadingEffect(fetchApi)
    console.log(result)
  } catch (error) {
    console.log('error', error)
  }
}
// fetch data from reddit
function fetchApi () {
  return fetch('https://www.reddit.com/new.json')
    .then(
      (response) => {
        return Promise.reject('haha')
        // return response.json()
      }
    )
    .catch(
      (error) => Promise.reject(error)
    )
}
const delay = (ms) => new Promise((resolve) => setTimeout(() => resolve(true), ms))

export function * callWithLoadingEffect (fn) {
  try {
    const task = yield fork(fn)
    const taskPromise = new Promise((resolve, reject) => {
      task.done.then(resolve, reject)
    })
    let {timeout, result} = yield race({
      timeout: call(delay, 100),
      result: taskPromise
    })
    if (timeout) {
      yield put({
        type: 'SHOW_LOADING'
      })
      result = yield taskPromise
    }
    yield put({
      type: 'HIDE_LOADING'
    })
    return result
  } catch (err) {
    yield put({
      type: 'HIDE_LOADING'
    })
    throw err
  }
}
export function * helloSaga () {
  console.log('Hello Sagas!')
  while (true) {
    yield take('FETCH')
    yield fork(api)
  }
}

If I returned Promise.reject('haha') when response comes back, proc.js would print errors:

screen shot 2016-04-10 at 18 28 27

I thought the try {} catch () {} in callWithLoadingEffect would catch the error as doc said, and from the console, I can see api generator did catch the error, but that uncaught haha seems like that it isn’t true, any suggest?

Thanks.

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Reactions:1
  • Comments:16 (16 by maintainers)

github_iconTop GitHub Comments

2reactions
yelouaficommented, Apr 25, 2016

I think you can get the same effect and a simpler code with try/finally

export function * callWithLoadingEffect (fn) {
  if (typeof fn !== 'function') {
    throw new Error('callWithLoadingEffect only accept function as its argument')
  }
  try {
    const task = yield fork(fn)
    let {timeout, result} = yield race({
      timeout: call(delay, 20),
      result: join(task)
    })
    if (timeout) {
      yield put(loadingShow())
      result = yield join(task)
    }
    return result
  } finally {
    yield put(loadingHide())
  }
}

IMO, you shouldn’t use any try/catch with a join effect. The main purpose of a join is to wait for a task to terminate. The task should handle all its errors and let only bubble unexpected ones (see below)

I’d rather separate errors in 2 classes

  • Business errors are expected errors and should be handled inside the forked task itself.
  • Bugs are unexpected errors and will simply bubble up the fork chain up to the root. All sagas in the current execution tree will be cancelled and the error will bubble up. I can’t see how a program can recover from a bug. The only options I think of is to show some message to the user that the app has encountered an unexpected error, send some report then close the app to avoid any inconsistent state.

At least the best way to work with the redux-saga fork model is to handle business errors from inside the forked tasks themselves an let bubble only bugs which can be caught at some higher level.

In fact I can even go farther and recommend to not use try/catch for business error handling because javascript IMO lacks the necessary constructs for typed catch blocks. consider this code

function* myCallabletask() {
  try {
    yield call(Api.gett) // note typo
    yield put(RequestSuccess)
  } catch(err) {
    yield put(RequestError) // will put on any kind of error including Runtime errors/bugs
  }
}

because the catch block catches all errors: both business Errors (server error, timeout …) and bugs (Api.gett is not function). The code reacts the same on both types; I don’t think this the desired behavior. Typically we want to put an Error action only on server errors and let bubble any bug. In typed languages like Java you can achieve this with typed catch blocks likecatch (ServerError err) But JS lacks this feature.

The best way to think of the fork model is as dynamic parallel effect. A saga has a main flow (its own body => the main task) and can fork parallel flows dynamically. Like in parallel effects, cancelling the saga will cancel all the parallel tasks (main task + forked tasks). The saga will terminate after all parallel tasks terminate, and an error from one of the tasks will abort the whole parallel effects.

It may sound restrictive but this has the advantage of having a much simpler model to reason about. You know precisely how do Return values, Errors and Cancellations propagate, … The other option is the very flexible Promise model but also its well known issues (esp. Error propagation => unhandled rejections, error swallowed, not to mention the difficulty to define a simple and consistent model for Cancellations)

0reactions
chenxsancommented, Apr 25, 2016

Thanks, I think it makes sense to separate those errors in my code.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Promise reject() causes "Uncaught (in promise)" warning
This happens because you do not attach a catch handler to the promise returned by the first then method, which therefore is without...
Read more >
Promise.reject() - JavaScript - MDN Web Docs
The Promise.reject() method returns a Promise object that is rejected with a given reason.
Read more >
Error handling with promises - The Modern JavaScript Tutorial
Promise chains are great at error handling. When a promise rejects, the control jumps to the closest rejection handler.
Read more >
Promises - Error Handling - Beginner JavaScript - Wes Bos
What does that log "Uncaught (in promise)" mean? It means that there was an error in one of our promises, but we did...
Read more >
Tracking Unhandled Promise Rejections - TrackJS
When a promise is rejected, it looks for a rejection handler. If it finds one, like in the example above, it calls the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found