Javascript síncrono com fibras

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.makeSyncFnFiber.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