1. 序
网页弹框是个很常见的功能,比如需要告知用户消息的时候 (Alert),需要用户进行确认的时候 (Confirm),需要用户补充一点信息的时候 (Prompt) …… 甚至可以弹框让用户填写表单 (Modal Dialog)。
弹框之后,开发者需要知道这个弹框是什么时候关闭以便进行接下来的操作。
在比较古老的 UI 组件中,这个事情是通过事件回调来进行的,大概长这样:
showDialog(content, title, {
closed: function() { console.log("对话框已关闭"); }
})
不过对话框的行为。你看,它弹出来了,但它不会阻塞后面的代码,而且开发者并不知道什么时候关闭,因为这是用户行为。既然是异步,封装成 Promise 使用 await
语法来调用会更舒服一些。简单的封装大概可以这样:
async function asyncShowDialog(content, title, options) {
return new Promise(resolve => {
showDialog(content, title, {
...options,
closed: resolve
});
});
}
(async () => {
await asyncShowDialog(content, title);
console.log("对话框已关闭");
})();
弹框的基本的异步行为就是这么简单,就这么结束?心有不甘,再研究研究!
2. 找两个弹框组件看看
Ant Design Vue 使用了事件的形式,点击“确定”按钮会触发 ok
事件,点击“取消”或者右上角的关闭按钮会触发 cancel
事件。
这两个事件处理函数通过参数对象的 onOk
和 onCancel
属性挂载进去。看起来平淡无奇,但如果处理事件返回的是一个 Promise 对象,点击按钮之后会出现加载动画并等待直到 Promise 对象完成之后才会关闭对话框。这种设计把异步等待动画组合到弹框当中,简洁直观,代码写起来也很方便。以 confirm 对话框为例:
Modal.confirm({
...
onOk() {
// 点击「确定」按钮后,会显示加载动画,并在一秒后关闭对话框
return new Promise(resolve => {
setTimeout(resolve, 1000);
});
}
...
});
而 Element Plus 使用了 Promise 形式,打开对话框时,并不是把确定或取消的处理函数以参数的形式传入,而是直接返回一个 Promise 对象,供开发者通过 .then()/.catch()
或者 await
处理。示例:
try {
await ElMessageBox.confirm(...);
// 按下确定按钮在这里处理
} catch(err) {
// 按下取消按钮在这里处理
}
Element Plus 的这种处理方式,要在对话框关闭之后才能处理业务。这也是使用 Promise 的局限 —— 对于一个已经封装好的 Promise 对象,很难在其中插入新的逻辑。
如果使用 ElMessageBox
的时候也想像 Ant Design 那样在关闭前进行一些异步操作,只能去找找看它是否提供了关闭前的处理事件。一找还真找到了,它有 beforeClose
事件。该事件的处理函数签名是 beforeClose(action, instance, done)
:
action
表示按了哪个按钮,取值可能是"confirm"
、"cancel"
和