javascript - Node.js - cache de módulos, objetos parcialmente construídos e dependências cíclicas?
Na documentação do node.js sobre cache de módulos, é feita a seguinte declaração:
Múltiplas chamadas a require('foo') podem não fazer com que o código do módulo seja executado várias vezes. Esta é uma funcionalidade importante. Com ela, objetos "parcialmente construídos" podem ser retornados, permitindo assim que dependências transitivas sejam carregadas mesmo quando causariam ciclos.
Estou um pouco confuso com a última frase. O que é um objeto 'parcialmente feito'? Como é que isto se relaciona com permitir (ou evitar) dependências cíclicas?
Resposta #1
Se você require um pacote a partir de um ficheiro, e isso fizer com que um ficheiro nesse pacote require o ficheiro que causou o require inicial, então você tem uma dependência cíclica. Por padrão, isso iria apenas entrar em círculos. Para evitar isso, pode-se manter um marcador onde o require original começou, de modo que na próxima vez que esse ficheiro for require'd, começará a partir desse ponto em vez do início. Não é perfeito, mas no caso de carregar um pacote, geralmente você está apenas interessado nas exportações, e funciona bem nesse caso.
Eu enviei um patch para o node-browserify há algum tempo para um método primitivo de exportações "parcialmente concluídas". Basicamente, cada vez que algo é require'd, ele verifica a quantidade de exportações. Se houver mais exportações, isso significa que o pacote estava incompleto na última vez e ainda pode estar a processar. Se não houver novas exportações (a nova contagem e a antiga são iguais), isso significa que o pacote está concluído e pode ser armazenado em cache para que o código do módulo não seja executado várias vezes. Como está no navegador, não há controlo sobre o fluxo de execução e, portanto, o código do módulo seria repetido parcialmente (em etapas) até estar concluído. Enquanto isso, tenho a certeza de que o Node.js tem uma gestão mais elegante.
Resposta #2
Existe um exemplo agradável e claro fornecido na documentação do node.js. Ele esclarece mais sobre o objeto "parcialmente feito" e as dependências cíclicas.
Quando existem chamadas circulares de require(), um módulo pode não ter terminado de executar quando é retornado.
Considere esta situação:
a.js:
console.log('a starting');
exports.done = false;
const b = require('./b.js');
console.log('in a, b.done = %j', b.done);
exports.done = true;
console.log('a done');
b.js:
console.log('b starting');
exports.done = false;
const a = require('./a.js');
console.log('in b, a.done = %j', a.done);
exports.done = true;
console.log('b done');
main.js:
console.log('main starting');
const a = require('./a.js');
const b = require('./b.js');
console.log('in main, a.done=%j, b.done=%j', a.done, b.done);
Quando main.js carrega a.js, então a.js por sua vez carrega b.js. Nesse ponto, b.js tenta carregar a.js. Para prevenir um ciclo infinito, uma cópia incompleta do objeto exports de a.js é retornada ao módulo b.js. b.js então termina de carregar, e o seu objeto exports é fornecido ao módulo a.js.
Quando o main.js tiver carregado ambos os módulos, eles já estarão ambos concluídos. A saída deste programa seria, portanto:
node main.js
main starting
a starting
b starting
in b, a.done = false
b done
in a, b.done = true
a done
in main, a.done=true, b.done=true
