ZeptoのCallbacksモジュールは、Deferredオブジェクトの基盤となるコールバック管理機構を提供します。このモジュールは非同期処理の柔軟な制御を可能にし、AjaxモジュールのPromiseスタイル実装を支えています。以下、その内部仕組みを詳細に解説します。
CallbacksモジュールはZeptoオブジェクトに追加されるファクトリ関数で、オプション設定によりコールバック管理オブジェクトを生成します。
(function($) {
$.Callbacks = function(config) {
// 実装コード
};
})(Zepto);
configパラメータには以下の動作制御フラグを指定可能:
- once: コールバックを1回のみ実行
- memory: 最終実行時の引数を保持し新規追加時に即時実行
- stopOnFalse: false返却時に処理中断
- unique: 同一関数の重複追加禁止
実際の使用例を確認します。
const handler = $.Callbacks({ memory: true });
const processX = (val) => console.log('X:', val);
const processY = (val) => console.log('Y:', val);
const processZ = (val) => console.log('Z:', val);
handler.add(processX).add(processY).add(processZ);
handler.remove(processZ);
handler.fire('initial'); // X: initial, Y: initial
handler.lock();
handler.fire('locked'); // 出力なし
handler.add((val) => console.log('after lock:', val));
// after lock: initial
handler.disable();
handler.add((val) => console.log('disabled:', val));
// 何も出力されない
memoryオプション有効時、lock後に追加したコールバックも直ちに実行されます。disable後は追加処理が無効化されます。
fireメソッドはコールバック実行の中心処理です。
fire = function(args) {
const [ctx, params] = args;
if (config.memory) lastArgs = { ctx, params };
isTriggered = true;
let currentPos = startIdx || 0;
startIdx = 0;
const listSize = callbacks.length;
isProcessing = true;
while (callbacks && currentPos < listSize) {
const result = callbacks[currentPos].apply(ctx, params);
if (result === false && config.stopOnFalse) {
lastArgs = null;
break;
}
currentPos++;
}
isProcessing = false;
if (callbacks) {
if (pendingList) {
if (pendingList.length) fire(pendingList.shift());
} else if (lastArgs) {
callbacks.length = 0;
} else {
disable();
}
}
};
引数をコンテキストとパラメータに分解し、memoryモードでは保存します。コールバックを順次実行し、stopOnFalse有効時はfalse返却で中断。実行後、待機キューがあれば処理し、memoryモード時はリストをクリアします。
addメソッドはコールバック追加処理です。
add: function() {
if (callbacks) {
const origLength = callbacks.length;
const addCallback = (items) => {
items.forEach(item => {
if (typeof item === 'function') {
if (!config.unique || !has(item)) callbacks.push(item);
} else if (Array.isArray(item)) {
addCallback(item);
}
});
};
addCallback(arguments);
if (isProcessing) {
listSize = callbacks.length;
} else if (lastArgs) {
startIdx = origLength;
fire(lastArgs);
}
}
return this;
},
追加時のリスト長を記録し、実行中はリストサイズを更新。memoryモード時は新規追加分を即時実行します。
removeメソッドは指定コールバックの削除処理です。
remove: function() {
if (callbacks) {
Array.from(arguments).forEach(target => {
let pos = -1;
while ((pos = callbacks.indexOf(target, pos + 1)) !== -1) {
callbacks.splice(pos, 1);
if (isProcessing) {
if (pos < listSize) listSize--;
if (pos < currentPos) currentPos--;
}
}
});
}
return this;
},
実行中はインデックス調整を行い、処理の整合性を維持します。
fireWithメソッドはコンテキスト指定での実行処理です。
fireWith: function(ctx, params) {
if (callbacks && (!isTriggered || pendingList)) {
params = params || [];
const args = [ctx, Array.isArray(params) ? params : [params]];
if (isProcessing) {
pendingList.push(args);
} else {
fire(args);
}
}
return this;
},
lockメソッドとdisableメソッドの差異はmemoryモード時の挙動にあります。lockはpendingListを無効化し、memory存在時はdisableされませんが、disableは全状態をクリアします。