Vue: errorHandler not working in Promises / async

Created on 15 Feb 2018  ·  17Comments  ·  Source: vuejs/vue

Version

2.5.13

Reproduction link

https://jsfiddle.net/zr7rz6xh/8/

Steps to reproduce

Vue.config.errorHandler = function (err, vm, info) {
alert("handler");
};

new Vue({
el: '#app',
mounted: function() {
return new Promise(function(resolve, reject) {
throw new Error('err');
});
}
})

What is expected?

errorHandler should be called

What is actually happening?

Uncaught (in promise) Error: err


Vue.config.errorHandler is not called if the error occurs inside a promise or await/async function.

I want to use the created hook with an await call, so it has to be async. But then the errorHandler is ignored.

feature request has PR improvement intend to implement

Most helpful comment

I like that idea, since async/await is getting more common that would probably be a nice thing to have.

All 17 comments

I like that idea, since async/await is getting more common that would probably be a nice thing to have.

This feature would be a great add! Here's another example of how it currently works:

new Vue({
  el: '#app',
  methods: {
    async asyncMethod() {
      throw new Error('This does NOT get caught by Vue') // wish this were caught
    },
    regularMethod() {
      throw new Error('This gets caught by Vue') // yay!
    },
  },
})

As a workaround, you can catch the error in a try / catch block. But I couldn't find a way to trigger an error on the Vue instance. Is this possible?

  methods: {
    async asyncMethod() {
      try {
        throw new Error('This does NOT get caught by Vue')
      } catch (err) {
        // Can you trigger the Vue instance's 'errorCaptured' callback from here?
      } 
    } 
  },

I thought maybe the convention of emitting an error event might work. I think that or something similar could be helpful.

this.$emit('error', error) // no special treatment of 'error' event

I think it is very comfortable for Vuex actions. I wish to be implemented asap.

vuex/actions.js

    async POST_SOMETHING({commit}, params) {
        await requireAuthAction()

        const { data } = await axios.post(`/api/something`, params)
        commit("POST_SOMETHING", data)
    },

main.js

Vue.config.errorHandler = (err) => {
    alert(err.message)
}

Until this issue is fixed I am using a hacky mixin to catch errors thrown in async methods and send them to errorHandler:

import Vue from 'vue'

// This mixin fixes following issue:
// errorHandler does not work with async component methods
// https://github.com/vuejs/vue/issues/7653

export default {
  beforeCreate() {
    const methods = this.$options.methods || {}
    Object.entries(methods).forEach(([key, method]) => {
      if (method._asyncWrapped) return
      const wrappedMethod = function (...args) {
        const result = method.apply(this, args)
        const resultIsPromise = result && typeof result.then == 'function'
        if (!resultIsPromise) return result
        return new Promise(async (resolve, reject) => {
          try {
            resolve(await result)
          } catch (error) {
            if (!error._handled) {
              const errorHandler = Vue.config.errorHandler || console.error
              errorHandler(error)
              error._handled = true
            }
            reject(error)
          }
        })
      }
      wrappedMethod._asyncWrapped = true
      methods[key] = wrappedMethod
    })
  },
}

@Doeke Any way to get that working with the errorCaptured hook? (from a glance it doesn't seem like it would)

@ErikBjare you could loop through this component and $parents after or before errorHandler(error) to check for existance of vm.$options.errorCaptured hooks. Personally I went back to catching exceptions manually again in components, and using unhandledrejection/rejectionhandled to send actual unhandled rejections to analytics (which only works on Chrome)

Hi, I expanded Doeke's mixin to simulate propagation to errorCaptured hooks, which may also be async. Not sure it works 100% to spec, but it may help someone temporarily until this issue is resolved:

import Vue from 'vue'

// This mixin fixes following issue:
// errorHandler does not work with async component methods
// https://github.com/vuejs/vue/issues/7653

async function propagateErrorCaptured(component, error, vm) {
    let continuePropagation = true
    const ec = component.$options.errorCaptured
    if (ec instanceof Array) {
        for (let i = 0; i < ec.length; i++) {
            continuePropagation = ec[i].apply(component, [error, vm])
            if (typeof continuePropagation === "object" && typeof continuePropagation.then === "function") {
                // wait for the promise
                continuePropagation = await continuePropagation
            }
            if (!continuePropagation) break;
        }
    }
    if (component.$parent && continuePropagation) {
        return await propagateErrorCaptured(component.$parent, error, vm)
    } else {
        return continuePropagation
    }
}

export default {
    beforeCreate() {
        const methods = this.$options.methods || {}
        Object.entries(methods).forEach(([key, method]) => {
            const wrappedMethod = function (...args) {
                const result = method.apply(this, args)
                const resultIsPromise = result && typeof result.then == 'function'
                if (!resultIsPromise) return result
                return new Promise(async (resolve, reject) => {
                    try {
                        resolve(await result)
                    } catch (error) {
                        const continuePropagation = await propagateErrorCaptured(this, error, this)
                        if (!continuePropagation) {
                            if (Vue.config.errorHandler) {
                                Vue.config.errorHandler(error, this)
                            } else {
                                reject(error)
                            }
                        }
                    }
                })
            }
            methods[key] = wrappedMethod
        })
    },
}

@Doeke Hi, your code is nice, but if it catch error by reject('......') ?

Closed via #8395 (will be out 2.6)

Is there a way for me to install [email protected] before it's released? I tried specifying https://github.com/vuejs/vue.git#2.6 in my dependencies, but then I get an error about

vue-template-compiler must be installed as a peer dependency, or a compatible compiler implementation must be passed via options.

I have vue-template-compiler in my devDependencies (^2.5.17) but for some reason it's not finding it, even though Vue 2.5.17 did.

If not, I'll just wait until 2.6 is out, but this is something we just recently found we needed. Thanks.

@daviesdoclc we just released 2.6.0-beta.1

@yyx990803 thanks. I just tried it but it's not solving my issue. It does solves the initial issue addressed at the beginning of this thread. However this still spits out "Uncaught (in promise) Error" in my case where I'm calling a plain javascript service for example (simplified below).

    function service() {
        return new Promise((resolve, reject) => {
            reject(new Error("err"))
        })
    }

    Vue.config.errorHandler = function(err, vm, info) {
        console.log("errorHandler", err)
    }

    new Vue({
        el: "#app",
        mounted: function() {
            // ** THIS WORKS ***
            // return new Promise((resolve, reject) => {
            //     reject(new Error("err"))
            // })
            // ** THIS DOESN'T ***
            service()
        }
    })

Suggestions on how to handle this pattern?

(if I shouldn't be commenting on a closed issue let me know)

@daviesdoclc return service()?

@yyx990803 sorry, that was a bad example. Yes, putting return on that does work in that scenario. However, that isn't my real world scenario, I was just trying to simplify. Here's where I'm having trouble.

    created() {
        AppJsonService.getTopForGenre(this.genre).then((_ranks) => {
            this.ranks = _ranks
        })
        // it would be nice not to have to do this and have it handled by the Vue.config.errorHandler
        // .catch((error) => {
        //    console.log(error)
        // })
        this._scrollCount = 0
    }

During created I am calling several services to get and set data elements. In this particular case it would be nice to not have to catch everything, but instead have it call the general error handler. This was the case I hoped it would work with.

I found this blog entry https://blog.bugsnag.com/anatomy-of-a-javascript-error/ that describes this situation. The suggestion is to either use a catch statement for all promise chains OR use unhandledrejection (which is only supported on Chrome right now). Sorry to have confused this with The Vue issue mention in this ticket.

Unless I'm missing something, https://github.com/f2009/vue/commit/c6c6d795bbb21453fb1aa027c76ce7c79edd4c6d doesn't seem to account for async watcher callbacks. JSFiddle

I'm also facing some issues with async methods.

My project uses Typescript through https://github.com/kaorun343/vue-property-decorator and even with Vue 2.6 when doing: throw new Error('aaa') it's not working in an public async mounted() whereas return Promise.reject('aaa') will trigger the errorCaptured of my main component.

Was this page helpful?
0 / 5 - 0 ratings