Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

errorHandler not working in Promises / async #7653

Closed
plehnen opened this issue Feb 15, 2018 · 18 comments
Closed

errorHandler not working in Promises / async #7653

plehnen opened this issue Feb 15, 2018 · 18 comments

Comments

@plehnen
Copy link

plehnen commented Feb 15, 2018

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.

@nickmessing
Copy link
Member

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

@techpines
Copy link

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

@01045972746
Copy link

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)
}

@Doeke
Copy link

Doeke commented Jul 13, 2018

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
    })
  },
}

@ErikBjare
Copy link

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

@Doeke
Copy link

Doeke commented Aug 28, 2018

@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)

@jblazevic
Copy link

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
        })
    },
}

@miser
Copy link

miser commented Oct 18, 2018

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

@yyx990803
Copy link
Member

Closed via #8395 (will be out 2.6)

@daviesdoclc
Copy link

Is there a way for me to install vue@2.6 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.

@yyx990803
Copy link
Member

@daviesdoclc we just released 2.6.0-beta.1

@daviesdoclc
Copy link

daviesdoclc commented Jan 16, 2019

@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)

@yyx990803
Copy link
Member

@daviesdoclc return service()?

@daviesdoclc
Copy link

daviesdoclc commented Jan 17, 2019

@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.

@daviesdoclc
Copy link

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.

f2009 pushed a commit to f2009/vue that referenced this issue Jan 25, 2019
@shengslogar
Copy link

Unless I'm missing something, f2009@c6c6d79 doesn't seem to account for async watcher callbacks. JSFiddle

@sneko
Copy link

sneko commented Apr 8, 2019

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.

@fastereder
Copy link

This is working for me:

window.addEventListener('unhandledrejection', event => { event.preventDefault() ... })

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests