2.2.6
https://jsfiddle.net/50wL7mdz/27555/
<component @click="callback">
</component>
function callbcak() {
return new Promise(function(resolve, reject) {
setTimeout(() => {
resolve('resolved')
}, 2000)
})
}
// in component
<script>
...
methods: {
handleClick(evt) {
var promise = this.$emit('click', evt)
console.log(promise) // promise is component self, not the return promise
}
}
...
</script>
expect $emit get the return value of event's callback
get component self
This would be a breaking change. Just emit a callback.
+1
This would be a breaking change. Just emit a callback.
An alternative would be to use a common pattern from Web APIs where you provide a method on the emitted event called something like waitUntil
that takes a promise.
For example:
<component @click="callback">
</component>
function callback(evt) {
evt.waitUntil(new Promise(function(resolve, reject) {
setTimeout(() => {
resolve('resolved')
}, 2000)
}))
}
// in component
<script>
...
methods: {
handleClick(evt) {
var promise = Promise.resolve()
evt.waitUntil = p => promise = p
this.$emit('click', evt)
console.log(promise) // promise is a Promise
}
}
...
</script>
If desired, you could get more robust and throw an error if waitUntil
is called more than once.
Nice vision @dlongley, you see beyond to the edge!!
You could also just pass the resolve
parameter directly to $emit
like so:
methods: {
handleClick(evt) {
var result = new Promise((resolve) => this.$emit('click', evt, resolve))
result.then((value) => console.log(value))
return result
}
}
That way you're not mutating an object that you didn't create ;) The receiver would consume it as the second argument:
function callback(evt, resolve) {
setTimeout(() => resolve('resolved'), 2000)
}
@sirlancelot,
This forces the receiver to call resolve
in order to achieve proper operation and eliminates the ability for them to reject with sane (and automatic) error propagation.
The waitUntil
approach allows the receiver to take some asynchronous action, but only if necessary, and provides for proper error propagation. I don't think there's a good reason to break the abstraction. Considerable thought was put into this design pattern in various Web APIs (e.g. ServiceWorkers) so I would recommend that approach. Of course, other patterns will work.
@dlongley awesome approach that I had not thought of yet!
Would it be possible to use that technique on something like this? I wrap callAfterLoggedIn
around my function that I want to be retried if the token needed to be refreshed. The problem I'm having is that I need the value returned from the promise on the second try. The first try the value is returned properly. I can't find out how to return the promise after attaching it to the 'tokenRefreshed' event.
export function callAfterLoggedIn(fn){
if(store.getters.loggedIn){
return callTryTokenRefresh(fn)
}else{
store.state.bus.$once('loggedIn', fn)
}
}
export function callAfterTokenRefreshed(fn){
store.state.bus.$once('tokenRefreshed', fn)
}
export function callTryTokenRefresh(fn){
let possiblePromise = fn()
let promise = possiblePromise instanceof Promise
if(promise){
return possiblePromise.then(null, () => {
return callAfterTokenRefreshed(fn)
})
}
return possiblePromise
}
For those interested in @dlongley comment: https://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent/waitUntil
I recently had an issue where I needed to do this in one of my event buses. I wrote a wrapper around the standard $emit using some ideas from this thread. It tidies up the API, and also allows only a single invocation of the promise per emit. Maybe not a 'best practice', but it is 'totally practical' AFAIAC...
Here's a POC codepen: https://codepen.io/Flamenco/pen/deqPvy?editors=1111
/**
* Adds an promise-like object with resolve/reject methods as the last argument, and returns a promise.
* @param topic The topic to emit
* @param varargs A 0..n arguments to send to the receiver
* @return {Promise<any>} A promise with the result.
* The receiver must call resolve or reject on the final argument.
*/
Vue.prototype.$emit_p = function (topic, varargs) {
return new Promise((resolve, reject) => {
const arr = Array.from(arguments)
let invoked = false
const promiseLike = {
resolve: val => {
if (!invoked) {
invoked = true
resolve(val)
}
},
reject: val => {
if (!invoked) {
invoked = true
reject(val)
}
}
}
arr.push(promiseLike)
this.$emit.apply(this, arr)
});
}
Publisher
this.$emit_p('add', 1, 2).then(res=>console.log(res))
Subscriber
this.$on('add', function(l,r,promise) {
setTimeout(()=>promise.resolve(l+r), 1000)
})
Could you pass the callback function as a prop instead of an event listener, then call the prop, e.g.:
Parent:
<template>
<v-child :callback="handleCallback" />
</template>
<script>
export default {
methods: {
handleCallback() {
return Promise.resolve();
}
}
}
</script>
Child:
props: {
callback: {
type: Function,
required: true,
},
},
methods: {
event() {
this.callback().then(() => {});
}
}
This makes sense to me given event emitters are agnostic to who's listening, but this _requires_ a return value
@samboylett that works for component based events but I needed something for an event bus that is stored in Vuex so this won't work
How to actually emit a callback? Examples from here doesn't work...
@yyx990803
This would be a breaking change. Just emit a callback.
I have such situation. Sometimes this.companies
will get updates, sometimes not:
parent component
fetchCompanies (resolve) {
this.$store.state.backend
.get('/jobBuilder/company/all')
.then(ret => {
console.log('companies fetched')
this.companies = ret.data
if(resolve){
resolve('resolved')
}
})
.catch(error => console.error(error))
}
child component
toggleActivation (button, company) {
button.disabled = true
let fetch = new Promise((resolve) => this.$emit('fetch', resolve)) //which activated fetchCompanies in parent
this.$store.state.backend
.post('/admin/update-activation/company', {
id: company.id,
active: !company.active
})
.then(() => fetch)
.catch(err => alert(err))
.finally(() => button.disabled = false)
}
And I'm not sure why, but API calls are not in the order I need them to be:
companies fetched
XHR finished loading: GET "http://localhost/jobBuilder/company/all"
companies watch activated
resolved
XHR finished loading: POST "http://localhost/admin/update-activation/company"
where it actually should be:
XHR finished loading: POST "http://localhost/admin/update-activation/company"
XHR finished loading: GET "http://localhost/jobBuilder/company/all"
companies watch activated
companies fetched
resolved
Most helpful comment
An alternative would be to use a common pattern from Web APIs where you provide a method on the emitted event called something like
waitUntil
that takes a promise.For example:
If desired, you could get more robust and throw an error if
waitUntil
is called more than once.