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, a seguinte afirmação é feita:
Múltiplas chamadas para require('foo') podem não fazer com que o código do módulo seja executado várias vezes. Esta é uma característica importante. Com ela, objetos "parcialmente concluídos" podem ser retornados, permitindo assim que dependências transitivas sejam carregadas mesmo quando elas causariam ciclos.
Estou um pouco confuso sobre a última frase. O que é um objeto 'parcialmente feito'? Como isso se relaciona com permitir (ou evitar) dependências cíclicas?
Resposta #1
Se você require um pacote de um arquivo, e isso faz com que um arquivo nesse pacote require o arquivo que causou o require inicial, então você tem uma dependência cíclica. Por padrão, isso iria apenas entrar em um ciclo. Para evitar isso, pode-se manter um marcador de onde o require original começou, de modo que na próxima vez que esse arquivo for requirecido, ele começará a partir desse ponto, em vez do início. Não é perfeito, mas no caso do carregamento de um pacote, você geralmente se interessa apenas pelas 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 verificará a quantidade de exportações. Se houver mais exportações, isso significa que o pacote estava incompleto na última vez e ainda pode estar sendo processado. 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á controle sobre o fluxo de execução e, portanto, o código do módulo seria repetido parcialmente (em etapas) até ser concluído. Enquanto tenho certeza de que o Node.js tem um manuseio mais elegante.
Resposta #2
Há um exemplo bom e claro fornecido na documentação do node.js. Ele esclarece mais sobre o objeto "parcialmente feito" e dependências cíclicas.
Quando existem chamadas circulares de require(), um módulo pode não ter terminado de ser executado 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 evitar um loop infinito, uma cópia não finalizada do objeto exports de a.js é retornada ao módulo b.js. b.js então termina de carregar, e 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
