Exposing and managing internal property open of RowAction could be not a perfect design pattern. Just provide a resolve/reject pair in the delete event to narrow scope of the problem to the event callback only:
Playground
RowAction.vue:
<script setup lang="ts" >
import {ref} from 'vue';
const open = ref(false);
const error = ref('');
const disabled = ref(false);
const $emit = defineEmits<{ (e: 'delete', defer: () => ReturnType<PromiseConstructor['withResolvers']>) }>()
function onDelete(){
let needsResolving = false;
error.value = '';
$emit('delete', () => {
needsResolving = true;
disabled.value = true;
const {promise, resolve, reject} = Promise.withResolvers();
promise
.then(() => open.value = false, e => error.value = e.message)
.finally(() => disabled.value = false);
return {promise, resolve, reject};
});
needsResolving || close();
}
function close(){
open.value = false;
error.value = '';
}
</script>
<template>
<div>
<button @click="open = true">
delete
</button>
<dialog :open>
<div> are you sure to delete ? </div>
<div v-if="error" style="font-size: small;color: red;">{{ error }}</div>
<button :disabled @click="close">close</button>
<button :disabled @click="onDelete">delete</button>
</dialog>
</div>
</template>
App.vue
<script setup lang="ts">
import {h} from 'vue';
import RowAction from './RowAction.vue';
class Data{
static remove(){
return new Promise((resolve, reject) => {
Math.random() > .5 ?
setTimeout(resolve, 1000) :
setTimeout(() => reject(new Error('Operation has failed')), 1000);
});
}
}
function render(){
return h(RowAction, {
onDelete: async (defer) => {
const {resolve, reject} = defer();
Data.remove().then(resolve, reject);
}
});
}
</script>
<template>
<label>With promise work</label>
<render/>
<label>Immediate</label>
<row-action/>
</template>