Fiquei surpreso como é bom me livrar de intermináveis callbacks aninhados em javascript.
Eu estava brincando com fibras de nó , tentando tornar mais fácil escrever testes .. Um dia tive a ideia: e se uma função pudesse ser executada de forma assíncrona quando chamamos dentro da fibra e síncrona ao passar uma função de retorno de chamada. Veja como posso fazer isso:
var Fiber = require('fibers');
// can be called to convert multiple methods
Fiber.makeSync = function (receiver) {
for (var n = 1; n < arguments.length; n++) {
Fiber.makeSyncFn(receiver, arguments[n]);
}
};
Fiber.makeSyncFn = function(receiver, methodName, errorArgNum) {
var origFn = receiver[methodName];
if (origFn == undefined) {
throw "Object don't have property '" + methodName + "'";
}
receiver[methodName] = function () {
var lastArg = arguments[arguments.length - 1];
// check if it called inside Fiber
if (Fiber.current && typeof lastArg != 'function') {
var fiber = Fiber.current;
var newValue;
var args = Array.prototype.slice.call(arguments);
if (typeof errorArgNum == 'undefined') errorArgNum = 1;
args.push(function(data) {
// retrieve error from arguments (optional)
var error = arguments[errorArgNum];
if (error) {
throw error;
}
// assign result and resume fiber
newValue = errorArgNum == 0 ? arguments[1] : data;
fiber.run();
});
// call original function with fiber-aware callback
origFn.apply(this, args);
// pause and wait till resume
Fiber.yield();
return newValue;
} else {
origFn.apply(this, arguments);
}
};
};
module.exports = Fiber;
As fibras fornecem apenas uma coisa simples: interromper a execução e aguardar a retomada. Para cada função com retorno de chamada, podemos chamá-la, colocar fibra em pausa e, quando recebermos retorno de chamada, retome a pausa e continue a execução. O código torna-se não bloqueador, mas assíncrono.
Veja como você pode usá-lo:
Fiber.makeSyncFn(redisClient, 'get', 0); // 0 is number if error argument passed in callback
Fiber.makeSyncFn(redisClient, 'set', 0);
Fiber(function () {
var value = redisClient.get('some_key');
redisClient.get('some_key', value + 1);
}).run();
Quando o chamarmos, ele substituirá a função original. Se estiver presente e o último argumento não for uma função, ele irá executá-lo em um wrapper compatível com fibra, caso contrário, ele será executado da maneira usual.Fiber.makeSyncFn
Fiber.current
Você também pode corrigir o protótipo da mesma maneira:
Fiber.makeSyncFn(redis.RedisClient.prototype, 'set', 0);
Fiber.makeSyncFn(redis.RedisClient.prototype, 'get', 0);
Fiz alguns benchmarks: servidor http simples, lê chave do redis, aumenta em 1 e escreve no redis, estou criando nova fibra a cada requisição. Então eu comparo com o mesmo servidor http escrito de forma assíncrona.
correr 5000 vezes e ver:
| nome | tempo por solicitação |
| ———- | —————— |
| c / fibras | 0,751ms |
| assíncrono | 0,706ms |
Corri várias vezes, a diferença é de cerca de 0,1 ms – 0,02 ms
Outro ponto de referência é um contador, feito para eliminar o tempo de abertura e fechamento da fibra: em comparação com o código de callback clássico, a sobrecarga é de cerca de 0,01ms – 0,007ms, o tempo gasto para pausar e retomar uma fibra.
Para mim, fico muito feliz em tornar o código mais legível e sustentável, mesmo que precise negociar alguns microssegundos (ou milissegundos).
* Como um bônus, nos dá uma maneira de rastrear exceções com try-catch
Quando você NÃO deve usar fibras:
- Quando você deseja executar algumas coisas assíncronas em paralelo, por exemplo, consultar o banco de dados e fazer uma solicitação http
- Quando você quiser que seu código seja executado no navegador também
- Quando sua função de retorno de chamada recebe mais de um argumento e você precisa deles
Continue lendo: um artigo sobre Geradores vs Fibras