釋出訂閱作為一種常見的設計模式,在前端模組化領域可以用來解決模組迴圈依賴問題。
看一個簡單的示例
// 訊息中介軟體v1
var msghub = (function() {
var listener = [];
return {
on: function(type, cb, option) {
listener[type] = listener[type] || [];
option = option || {};
listener[type].push({
cb: cb,
priority: option.priority || 0
});
},
fire: function(type, dataObj) {
if (listener[type]) {
listener[type].sort((a, b) => a.priority - b.priority).forEach((item) => {
item.cb.call(null, dataObj);
});
}
}
}
})();
複製程式碼
以及訊息中介軟體的使用模組
// a.js
msghub.on('data', function(data) {
console.log(data.val + 1); // 3
})
// b.js
msghub.on('data', function(data) {
console.log(data.val + 2); // 4
})
// c.js
msghub.fire('data', {
val: 2
});
複製程式碼
當c模組觸發data事件的時候,a和b模組的監聽函式都會被執行並輸出相應的結果。
訂閱函式管道化
上面的例子基本可以滿足需求了,但是有時候希望多個訂閱函式之間可以傳遞執行結果,類似linux管道a.pipe(b).pipe(c)…這種,上一個函式的輸出是下一個函式的輸入。 針對管道化需求對msghub的回撥遍歷從forEach改為reduce方式,如下程式碼所示
// 訊息中介軟體v2 支援執行結果傳遞
var msghub = (function() {
var listener = [];
option = option || {};
return {
on: function(type, cb, option) {
listener[type] = listener[type] || [];
listener[type].push({
cb: cb,
priority: option.priority || 0
});
},
fire: function(type, dataObj) {
if (listener[type]) {
listener[type].sort((a, b) => b.priority - a.priority).reduce((pre, cur) => {
let result = cur.cb.call(null, pre) || pre; // 如果一個訂閱函式沒有返回值則傳遞上上個訂閱函式的執行結果,如果需要完全的管道化的話就把|| pre去掉即可
return result;
}, dataObj);
}
}
}
})();
複製程式碼
測試一下上面的msghub
// a.js
msghub.on('data', function(data) {
console.log('module a get num:' + data.val); // 3
return {
val: ++data.val
};
})
// b.js
msghub.on('data', function(data) {
console.log('module b get num:' + data.val)
return {
val: data.val + 3
}
})
// d.js
msghub.on('data', function(data) {
console.log('module d get num:' + data.val);
})
// e.js
msghub.on('data', function(data) {
console.log('module e get num:' + data.val);
})
// c.js
msghub.fire('data', {
val: 2
});
複製程式碼
使用改良後的msghub的話
// a.js
msghub.on('data', function(data) {
console.log('module a get num:' + data.val); // 3
return {
val: ++data.val
};
})
// b.js
msghub.on('data', function(data) {
console.log('module b get num:' + data.val)
return {
val: data.val + 3
}
})
// d.js
msghub.on('data', function(data) {
console.log('module d get num:' + data.val);
})
// e.js
msghub.on('data', function(data) {
console.log('module e get num:' + data.val);
})
// c.js
msghub.fire('data', {
val: 2
});
複製程式碼
最終列印輸出如下資訊:
module a get num:2
module b get num:3
module d get num:6
module e get num:6
複製程式碼
訂閱函式支援非同步
上面的例子中有一個問題就是訂閱函式必須是同步程式碼,如果a.js包含下述非同步程式碼的話就會出問題
// a.js
msghub.on('data', function(data) {
console.log('module a get num:' + data.val); // 3
return new Promise(function(resolve, reject) {
setTimeout(() => {
resolve({
val: ++data.val
})
}, 1000);
});
})
複製程式碼
針對可能非同步的情況我們需要進一步改良msghub來支援,該請asyn和await出場了
// 訊息中介軟體v3 完美支援同步、非同步管道化
var msghub = (function() {
var listener = [];
return {
on: function(type, cb, option) {
listener[type] = listener[type] || [];
option = option || {};
listener[type].push({
cb: cb,
priority: option.priority || 0
});
},
fire: function(type, dataObj) {
if (listener[type]) {
let listenerArr = listener[type].sort((a, b) => b.priority - a.priority);
(async function iter() {
let val = dataObj;
for (const item of listenerArr) {
val = await item.cb.call(null, val);
}
})();
}
}
}
})();
複製程式碼
注意: 上述程式碼可以在node環境做測試,如果需要在瀏覽器中執行的話,需要對for of和async await進行babel編譯