Pull to refresh

Удобный callback

JavaScript
Надоело каждый раз писать колбеки руками. Написал простенькую скриптину, которая запонимает функцию (функции) с массивом аргументов и контекстом в объекте с методом fire, который не зависит от this, чтобы можно было цеплять колбек не только в «чистом» коде, но и к онклику или таймеру. Набор исполняемых функций и аргументов/контекста к каждой из них произвольный.

Из пряников только возможность давить всплытие ошибок, ограничивать кол-во запусков колбека и удобство выпечки других пряников на свой вкус.

Сама скриптина

function _callback(fn, args, ctx, decorators) {
// создание колбек-объекта
// fn - вызываемая функция (обязательна):
// может быть функцией, объектом {fn: function, args: [], ctx: {}}, или массивом
// или смесью массивов, объектов и функций
// args - массив аргментов
// ctx - контекст (this) для вызываемой функции
// decorators - строчка или массив строчек с назаванием дектораторов,
// дополняющих основную функциональность
var ret = new _callback.Def(fn, args, ctx);
if (decorators) {
var d = _callback.decorators;
for (var key in decorators) {
if (key in d) ret = new d[key](ret, fn, args, ctx, decorators[key]);
}
}
return ret;
}
_callback.Def = function(fn, args, ctx) {
var dublicate = this; // myClb.fire можно просто передать в таймер или dom-событие,
// и ничего от этого не сломается
var arrayOrNull = function(ar) { return ar && ar.constructor == Array ? ar : null; };

setFn(fn);

this.updateFn = updateFn;
//this.__defineSetter__("fn", setFn); // ie
//this.__defineGetter__("fn", function() { return fn; });

function fireByFn(myArgs, myFn, myCtx) {
// вызываем колбек-функцию только здесь
(myFn || fn).apply(myCtx || ctx || null, arrayOrNull(myArgs) || arrayOrNull(args) || []);
return dublicate;
}
function fireByArray(myArgs, myFn, myCtx) {
// если вместо фукнции прилетел массив, выполним вызов для каждого элемента
for (var i=0, ar=myFn||fn, c=myCtx||ctx, a=arrayOrNull(myArgs) || arrayOrNull(args), l=ar.length; i<l; i++) getFireFn(ar[i])(a, ar[i], c);
return dublicate;
}
function fireByJSON(myArgs, myJson, myCtx) {
// если вместо фукнции прилетел JSON
var json = myJson || fn;
getFireFn(json.fn)(arrayOrNull(json.args) || arrayOrNull(myArgs), json.fn, json.ctx || myCtx);
return dublicate;
}

function getFireFn(fn) {
// определяем тип обратного вызова и возвращаем его
var type = dublicate._getTypeofFireFn(fn), fireFn;
switch(type) {
case "json": return fireByJSON; break;
case "array": return fireByArray; break;
case "function": return fireByFn; break;
}
return fireFn;
}

function setFn(val) {
// отдаем наружу правильный метод fire
dublicate.fire = getFireFn(fn = val);
}
function updateFn(transformFn) {
setFn(transformFn(fn));
return dublicate;
}
};
_callback.Def.prototype._err = function() { this._err.withoutFn(); };
_callback.Def.prototype._err.withoutFn = function() {
throw "_callback: wrong fn argument";
};
_callback.Def.prototype._getTypeofFireFn = function(fn) {
// чем является переданный объект: функцией, массивом, объектом
if (!fn) this._err.withoutFn();
if (typeof fn == "function") return "function";
if (fn.constructor == Array) {
for (var i=fn.length; i--;) this._getTypeofFireFn(fn[i]);
return "array";
} else {
// {}
this._getTypeofFireFn(fn.fn);
return "json";
}
this._err.withoutFn();
};
_callback.decorators = {};
_callback.decorators._copy = function(component, orig, instance) {
for (var key in component) orig[key] = instance[key] = component[key];
//instance.__defineSetter__("fn", function(val) { component.fn = val; }); // ie
//instance.__defineGetter__("fn", function() { return component.fn; });
};
_callback.decorators.count = function(component, fn, args, ctx, misc) {
var orig = {};
_callback.decorators._copy(component, orig, this);
var fired = misc;

this.fire = fire;

function fire() {
if (!fired) return;
fired--;
component.fire();
}
};
_callback.decorators.stopThrow = function(component, fn, args, ctx, misc) {
var orig = {};
_callback.decorators._copy(component, orig, this);
this.fire = fire;

function fire() {
try {
component.fire();
} catch(er) {}
}
};
_clb = _callback;


* This source code was highlighted with Source Code Highlighter.


Пример использования

Подготовим фукнции, которые будут откликаться при .fire
var res = "";
addLine = function(str) {
res += str +"\n";
};

function f1(q, w, e) {
addLine("f1: "+ q +", "+ w +", "+ e +"; this.q = "+ this.q);
}
function f2(q, w, e) {
addLine("f2: "+ q +", "+ w +", "+ e +"; this.q = "+ this.q);
}
function f3(q, w, e) {
addLine("f3: "+ q +", "+ w +", "+ e +"; this.q = "+ this.q);
}
function f4(q, w, e) {
addLine("f4: "+ q +", "+ w +", "+ e +"; this.q = "+ this.q);
}
function f5() {
uNdEfInEd++;
addLine("f5");
}


* This source code was highlighted with Source Code Highlighter.


И, собственно пример использования
// создадим пройстой объект колбека:
q = _clb(
f1, // функция, которую следует вызывать...
[1,2,3], // ...с тремя аргументами и...
{q:5} // ...в заданном контексте (this.q==5)
);
q.fire(); // просто выполним колбек
// f1: 1, 2, 3; this.q = 5
q.fire([8]); // выполним колбек с другими аргументами
// f1: 8, undefined, undefined; this.q = 5
q.fire(false, false, {q:11}); // функция и аргументы теже, но другой this
// f1: 1, 2, 3; this.q = 11
q.fire([]); // и без аргументов
// f1: undefined, undefined, undefined; this.q = 5
addLine("----"); // для удобочитаемости
// переобозначим колбэк
q = _clb([f1, f2, f3], [1,2,3], {q:5}); // аргументы и контекст теже, но вызываться будут три фукнции
q.fire();
// f1: 1, 2, 3; this.q = 5
// f2: 1, 2, 3; this.q = 5
// f3: 1, 2, 3; this.q = 5
q.fire([8]); // аргументы меняются вне зависимости от "функции"
// f1: 8, undefined, undefined; this.q = 5
// f2: 8, undefined, undefined; this.q = 5
// f3: 8, undefined, undefined; this.q = 5
addLine("----");

// можно использовать и JSON вперемешку с массивами и переопределить аргументы для отдельных вызовов:
q = _clb(
[
f1, // f1: 1, 2, 3; this.q = 5
{fn: f2, args: [4, 5, 6]}, // f2: 4, 5, 6; this.q = 5
{
fn: [
f2, // f2: 7, 8, 9; this.q = 0
{fn: f2, args: [4, 5, 6]}, // f2: 4, 5, 6; this.q = 0
f3 // f3: 7, 8, 9; this.q = 0
],
args: [7, 8, 9], // аргументы по умолчанию для этого блока
ctx: {q: 0} // контекст по умолчанию для этого блока
}
],
[1,2,3], // аргументы по умолчанию
{q:5} // контекст по умолчанию
).fire();
addLine("----");

// заменять список функий можно так:
q.updateFn(function(fn) {
// мы знаем, что у нас массив
fn.splice(1, 5);
fn.push(f1, f1);
return fn;
}).fire(); // трижды: f1: 1, 2, 3; this.q = 5
addLine("----");

// если не нужен ie, можно раскомментировать __defineSetter__ и __defineGetter__ в скрипте и делать так:
//q.fn = [f2, {fn: f2, args: [], ctx: {}}, {fn: f3, args: [7, 8, 9], ctx: {q: 0}}];
//q.fire();
//addLine("----");

// запрещаем всплытие ошибок:
q = _clb(f5, false, false, {stopThrow: true}).fire(); // ничего не выведется

// ограничиваем кол-во исполнений колбэка:
q = _clb(f1, [1,2,3], {q:5}, {count: 1});
q.fire(); // f1: 1, 2, 3; this.q = 5
// счетчик переключается с 1 на 0
q.fire(); // второй раз колбек не выстрелит

alert(res);


* This source code was highlighted with Source Code Highlighter.
Думаю, я не один такой умный и хотелось бы узнать другие варианты.
Tags:jscallback_clb
Hubs: JavaScript
Total votes 27: ↑13 and ↓14 -1
Views2.8K

Popular right now

Javascript разработчик
from 160,000 to 220,000 ₽ArtezioМосква
Javascript разработчик
from 2,000 $ArtezioВитебск
JavaScript разработчик
from 170,000 to 300,000 ₽WeMakeTeamRemote job
Frontend разработчик (JS)
to 250,000 ₽Stream LabsRemote job
JavaScript Software Engineer
from 3,000 to 5,500 $XpowerОдессаRemote job