postMessage消息通信Promise化的方法實現(xiàn)
前言
postMessage Api 想必大家都不陌生,WebWorker 通信會用到,iframe 窗口之間通信也會用到,尤其像一些通過 iframe 嵌入其他項目產(chǎn)品的應用,想要實現(xiàn)實時通信,就少不了它。
但是,監(jiān)聽消息基本都是全局注冊事件來接收對應消息,然后在做分發(fā)處理的。
假如業(yè)務比較復雜,流程比較長,消息通信的頻率高,而且通信場景繁瑣,那么通過全局事件處理就顯得不太合適了。
那么問題來了,我們能不能將 postMessage 進行一次轉(zhuǎn)化,把他變成類似 Promise 的使用方式,這樣業(yè)務里使用 postMessage 進行通信的時候,就不需要考慮全局回調(diào)事件了。而且 Promise 和他的語法糖 await 都可以很好的消除回調(diào)地獄的情況。
思考
Promise 化,需要解決那些問題?
- 業(yè)務中發(fā)起的事件,如何注冊并消費?
- 相同的事件多次發(fā)起,應該如何處理響應結(jié)果?
我們先來看業(yè)務中發(fā)起的事件,如何注冊并消費?
舉個實際的例子:我們假定有子系統(tǒng)向父級 iframe 獲取 loginToken 的行為。
正常情況我們會這樣寫:
const messageEventHandler = (event: MessageEvent) => {
const data = event.data;
// 分發(fā)事件
emit(data?.api, data);
}
// 接收
window.addEventListener("message", messageEventHandler);
// 發(fā)送
window.parent.postMessage({ api: "getLoginToken" }, "*");
這樣寫邏輯有問題嗎?沒有問題!邏輯可以正確的執(zhí)行,事件也成功的被分發(fā)了
通過消息訂閱來接收通知,單發(fā)的時候看起來沒什么問題,假如
getLoginToken被多次觸發(fā),或者說,同一個事件被多次注冊,那么還需要考慮業(yè)務側(cè)事件執(zhí)行后銷毀,避免重復觸發(fā)
有沒有辦法可以減輕業(yè)務側(cè)的心智負擔呢?
辦法總比困難多嘛~
我們可以使用一個全局變量
PostMessageCallBackMap來存放注冊的事件及回調(diào),Promise中的resolve正好有閱后即焚的特性,那么我們是不是可以考慮把創(chuàng)建出來的resolve作為callback放到Map中呢?
想到就立即行動!
實現(xiàn)
改造后的代碼如下:
export const PostMessageCallBackMap = new Map();
const messageEventHandler = (event: MessageEvent) => {
// 事件分發(fā)
const data = event.data;
let eventKey = data?.api;
if (PostMessageCallBackMap.has(eventKey)) {
PostMessageCallBackMap.get(eventKey)(data);
}
};
// 接收
window.addEventListener("message", messageEventHandler);
接收我們寫好了,發(fā)送這塊怎么實現(xiàn)呢?
// 發(fā)送
function sendMessage<T>(param: JSBridgeReq): Promise<JSBridgeRes<T>> {
return new Promise((resolve, reject) => {
if (window.parent) {
window.parent.postMessage(param, "*");
// 將當前的 resolve 添加到 Map 中,等待返回事件觸發(fā)
let eventKey = param.api;
PostMessageCallBackMap.set(eventKey, resolve);
}
});
}
// 這樣封裝一下,業(yè)務中使用就十分方便了
const { token } = await sendMessage({ api: "getLoginToken" })
console.log(token);
這樣看起來舒服了很多,業(yè)務中應用起來也順手了很多,完結(jié)撒花~
等等!
還有個問題吶。
相同的事件多次發(fā)起,應該如何處理響應結(jié)果?
陷入沉思
emm,按上面的寫法,多次發(fā)送 getLoginToken 事件,Map 中的 Key 是唯一的,之前的事件會被覆蓋掉,再加上異步事件返回時間不確定的話,完蛋了?。?/p>
擺爛!
注釋加上,不要調(diào)多次!
結(jié)束!
測試:哦?是嗎?(狂點、狂點、狂點)
好啦好啦,雖然我們可以通過防抖來避免測試瘋狂轟炸,但是某些場景下真的會有連續(xù)觸發(fā) api 的情況,那我們怎么解決呢?
每一個回調(diào)給他一個唯一id,然后通過事件id來進行匹配,不管事件執(zhí)行的時間長短,通過事件id總可以找到另一半。
唯一id,最簡單的我們就用時間戳吧。
改造!改造!改造!
export const PostMessageCallBackMap = new Map();
// 發(fā)送
function sendMessage<T>(param: JSBridgeReq): Promise<JSBridgeRes<T>> {
return new Promise((resolve, reject) => {
if (window.parent) {
param.timestamp = Date.now();
window.parent.postMessage(param, "*");
let eventKey = param.api + param.timestamp;
PostMessageCallBackMap.set(eventKey, resolve);
}
});
}
// 接收
const messageEventHandler = (event: MessageEvent) => {
// 事件分發(fā)
const data = event.data;
let eventKey = data?.api;
// timestamp 來保證對應關系
eventKey = data?.api + (data?.timestamp || "");
if (PostMessageCallBackMap.has(eventKey)) {
PostMessageCallBackMap.get(eventKey)(data);
// 由于事件id的存在,Map會越來越大,清除字典防止內(nèi)存泄漏
setTimeout(() => {
PostMessageCallBackMap.delete(eventKey);
}, 0);
}
};
window.addEventListener("message", messageEventHandler);
總結(jié)
通過這樣一番分析改造,終于讓難以控制的全局注冊事件 postMessage 變成了可以鏈式調(diào)用的 Promise,又可以愉快的寫業(yè)務代碼啦~
演示地址:https://ztstory.github.io/vue-composition-demo/#/PostMessagePromise
源碼地址:https://github.com/ZTStory/vue-composition-demo
到此這篇關于postMessage消息通信Promise化的方法實現(xiàn)的文章就介紹到這了,更多相關postMessage消息Promise化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
js前端實現(xiàn)圖片懶加載(lazyload)的兩種方式
本篇文章主要介紹了js前端實現(xiàn)圖片懶加載(lazyload)的兩種方式 ,使用圖片懶加載可以提高網(wǎng)頁運行速度,有興趣的可以了解一下。2017-04-04
Js 代碼中,ajax請求地址后加隨機數(shù)防止瀏覽器緩存的原因
Js 代碼中,ajax請求地址后加隨機數(shù)防止瀏覽器緩存的原因,需要的朋友可以參考一下2013-05-05
layui之table checkbox初始化時選中對應選項的方法
今天小編就為大家分享一篇layui之table checkbox初始化時選中對應選項的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-09-09

