node.js yêu cầu bộ đệm () - có thể làm mất hiệu lực?


325

Từ tài liệu của node.js:

Các mô-đun được lưu trữ sau lần đầu tiên được tải. Điều này có nghĩa (trong số những thứ khác) mà mọi cuộc gọi yêu cầu ('foo') sẽ nhận được chính xác cùng một đối tượng được trả về, nếu nó sẽ giải quyết cùng một tệp.

Có cách nào để vô hiệu hóa bộ đệm này? tức là để thử nghiệm đơn vị, tôi muốn mỗi thử nghiệm được làm việc trên một đối tượng mới.



Một mô-đun NPM khác có người theo dõi: npmjs.com/package/updated-require
Jorge Fuentes González

Bạn có thể lưu trữ nội dung tệp mà không cần sử dụng và yêu cầu nó cho các phạm vi khác nhau stackoverflow.com/questions/42376161/ chủ
lonewar Warrior556

Câu trả lời:


304

Bạn luôn có thể xóa một mục một cách an toàn trong request.cache mà không gặp vấn đề gì, ngay cả khi có các phụ thuộc vòng tròn. Bởi vì khi bạn xóa, bạn chỉ cần xóa một tham chiếu đến đối tượng mô-đun được lưu trong bộ nhớ cache, không phải chính đối tượng mô-đun, đối tượng mô-đun sẽ không được xử lý vì trong trường hợp phụ thuộc vòng tròn, vẫn còn một đối tượng tham chiếu đến đối tượng mô-đun này.

Giả sử bạn có:

tập lệnh a.js:

var b=require('./b.js').b;
exports.a='a from a.js';
exports.b=b;

tập lệnh b.js:

var a=require('./a.js').a;
exports.b='b from b.js';
exports.a=a;

khi bạn làm:

var a=require('./a.js')
var b=require('./b.js')

bạn sẽ nhận được:

> a
{ a: 'a from a.js', b: 'b from b.js' }
> b
{ b: 'b from b.js', a: undefined }

bây giờ nếu bạn chỉnh sửa b.js của mình:

var a=require('./a.js').a;
exports.b='b from b.js. changed value';
exports.a=a;

và làm:

delete require.cache[require.resolve('./b.js')]
b=require('./b.js')

bạn sẽ nhận được:

> a
{ a: 'a from a.js', b: 'b from b.js' }
> b
{ b: 'b from b.js. changed value',
  a: 'a from a.js' }

===

Ở trên là hợp lệ nếu trực tiếp chạy node.js. Tuy nhiên, nếu sử dụng các công cụ có hệ thống bộ nhớ đệm mô-đun riêng, chẳng hạn như jest , tuyên bố chính xác sẽ là:

jest.resetModules();

2
bạn có thể vui lòng giải thích tại sao { ... a: undefined}khi yêu cầu b.jslần đầu tiên? Tôi sẽ mong đợi bằng nhau 'a from a.js'. Cảm ơn bạn
ira

1
Tại sao không xác định?
Jeff P Chacko

4
Trả lời muộn, nhưng từ những gì tôi thu thập b[a]được không xác định lần đầu tiên vì có sự phụ thuộc vòng tròn. a.jsđòi hỏi b.jslần lượt yêu cầu a.js. a.jsvẫn chưa được tải đầy đủ và exports.achưa được xác định, vì vậy b.jskhông có gì.
nik10110

có cách nào để làm điều này nếu tôi đang sử dụng require.main.require(path)như được mô tả ở đây không? stackoverflow.com/questions/10860244/ từ
Flion

186

Nếu bạn luôn muốn tải lại mô-đun của mình, bạn có thể thêm chức năng này:

function requireUncached(module) {
    delete require.cache[require.resolve(module)];
    return require(module);
}

và sau đó sử dụng requireUncached('./myModule')thay vì yêu cầu.


6
Điều này là hoàn hảo kết hợp với fs.watchphương pháp lắng nghe các thay đổi tập tin.
ph3nx

2
rủi ro là gì?
Scarass

Tôi có cùng một câu hỏi, nguy cơ sử dụng giải pháp này là gì và không phải là câu trả lời được chấp nhận?
rotimi-best

1
Nó thực sự giống nhau. Tùy thuộc vào cách cấu trúc mã, mọi thứ có thể bị sập khi bạn cố gắng khởi tạo lại mã. Ví dụ. nếu mô-đun khởi động một máy chủ và lắng nghe một cổng. Lần sau khi bạn yêu cầu Sử dụng mô-đun, nó sẽ thất bại vì cổng đó đã được mở, v.v.
luff

133

Vâng, bạn có thể truy cập vào bộ nhớ cache qua require.cache[moduleName]nơi moduleNamelà tên của module bạn muốn truy cập. Xóa một mục bằng cách gọi delete require.cache[moduleName]sẽ gây rarequire tải tập tin thực tế.

Đây là cách bạn sẽ xóa tất cả các tệp được lưu trong bộ nhớ cache được liên kết với mô-đun:

/**
 * Removes a module from the cache
 */
function purgeCache(moduleName) {
    // Traverse the cache looking for the files
    // loaded by the specified module name
    searchCache(moduleName, function (mod) {
        delete require.cache[mod.id];
    });

    // Remove cached paths to the module.
    // Thanks to @bentael for pointing this out.
    Object.keys(module.constructor._pathCache).forEach(function(cacheKey) {
        if (cacheKey.indexOf(moduleName)>0) {
            delete module.constructor._pathCache[cacheKey];
        }
    });
};

/**
 * Traverses the cache to search for all the cached
 * files of the specified module name
 */
function searchCache(moduleName, callback) {
    // Resolve the module identified by the specified name
    var mod = require.resolve(moduleName);

    // Check if the module has been resolved and found within
    // the cache
    if (mod && ((mod = require.cache[mod]) !== undefined)) {
        // Recursively go over the results
        (function traverse(mod) {
            // Go over each of the module's children and
            // traverse them
            mod.children.forEach(function (child) {
                traverse(child);
            });

            // Call the specified callback providing the
            // found cached module
            callback(mod);
        }(mod));
    }
};

Cách sử dụng sẽ là:

// Load the package
var mypackage = require('./mypackage');

// Purge the package from cache
purgeCache('./mypackage');

Vì mã này sử dụng cùng một trình phân giải require, chỉ cần xác định bất cứ điều gì bạn muốn yêu cầu.


"Unix không được thiết kế để ngăn người dùng làm những điều ngu ngốc, vì điều đó cũng sẽ ngăn họ làm những điều thông minh." - Doug Gwyn

Tôi nghĩ rằng nên có một cách để thực hiện tải mô-đun không rõ ràng.


17
+1 chỉ cho trích dẫn của Doug. Tôi cần ai đó nói ra những gì tôi cũng tin vào :)
Poni 17/03/13

1
Câu trả lời tuyệt vời! Nếu bạn muốn bắt đầu một nút thay thế bằng tải lại kích hoạt, hãy kiểm tra ý chính này .
gleitz

1
tuyệt vời. Tôi sẽ thêm điều này vào require.uncachechức năng. `` `// xem github.com/joyent/node/issues/8266 Object.keys (module.constructor._pathCache) .forEach (function (k) {if (k.indexOf (moduleName)> 0) xóa module.constructor ._pathCache [k];}); `` `Giả sử bạn đã yêu cầu một mô-đun, sau đó gỡ cài đặt nó, sau đó cài đặt lại cùng một mô-đun nhưng sử dụng một phiên bản khác có tập lệnh chính khác trong gói.json, yêu cầu tiếp theo sẽ không thành công vì tập lệnh chính đó không tồn tại vì nó được lưu trữ trongModule._pathCache
bentael

tào lao Nhận xét của tôi là khủng khiếp. Tôi không thể thêm mã gọn gàng trong bình luận này và đã quá muộn để chỉnh sửa, vì vậy tôi đã trả lời. @Ben Barkay nếu bạn có thể chỉnh sửa câu hỏi của mình để thêm đoạn mã nhỏ vàorequire.uncache
bentael

Cảm ơn @bentael, tôi đã thêm câu này vào câu trả lời của mình.
Ben Barkay

39

Có một Mô-đun đơn giản cho điều đó ( với các bài kiểm tra )

Chúng tôi đã gặp sự cố chính xác này trong khi kiểm tra mã của mình ( xóa các mô-đun được lưu trong bộ nhớ cache để chúng có thể được yêu cầu lại ở trạng thái mới ) vì vậy chúng tôi đã xem xét tất cả các đề xuất của mọi người về các Câu hỏi & Trả lời StackOverflow khác nhau và kết hợp một mô-đun node.js đơn giản ( với các bài kiểm tra ):

https://www.npmjs.com/package/ decache

Như bạn mong đợi, hoạt động cho cả gói npm được xuất bản và các mô-đun được xác định cục bộ . Windows, Mac, Linux, v.v.

Xây dựng tình trạng codecov.io Mã duy trì khí hậu Tình trạng phụ thuộc Tình trạng devDependencies

Làm sao? ( sử dụng )

Cách sử dụng khá đơn giản:

Tải về

Cài đặt mô-đun từ npm:

npm install decache --save-dev

Sử dụng nó trong mã của bạn:

// require the decache module:
const decache = require('decache');

// require a module that you wrote"
let mymod = require('./mymodule.js');

// use your module the way you need to:
console.log(mymod.count()); // 0   (the initial state for our counter is zero)
console.log(mymod.incrementRunCount()); // 1

// delete the cached module:
decache('./mymodule.js');

//
mymod = require('./mymodule.js'); // fresh start
console.log(mymod.count()); // 0   (back to initial state ... zero)

Nếu bạn có bất kỳ câu hỏi hoặc cần thêm ví dụ, vui lòng tạo một vấn đề GitHub: https://github.com/dwyl/decache/issues


1
Tôi đã xem xét điều này và nó thực sự tuyệt vời đối với tôi khi sử dụng thử nghiệm để tôi có thể dỡ và tải lại một mô-đun trong các điều kiện cụ thể, nhưng không may là tôi đang làm việc và công ty của tôi tránh xa giấy phép GPL. Tôi chỉ muốn sử dụng nó để thử nghiệm vì vậy tôi vẫn đang xem xét nó bởi vì nó trông rất hữu ích.
Matt_JD

@Matt_JD cảm ơn phản hồi của bạn. bạn thích giấy phép nào
nelsonic

2
@Matt_JD Chúng tôi đã cập nhật giấy phép lên MIT. Chúc may mắn với công việc của bạn! :-)
nelsonic

1
Điều này đã làm việc tuyệt vời! Diễn viên này repo và nâng cao câu trả lời này.
aholt

1
rất khuyến khích, chạy tốt trên v14.2.0 mới nhất tính đến hôm nay
Thomazella

28

Đối với bất kỳ ai bắt gặp điều này đang sử dụng Jest, bởi vì Jest thực hiện bộ nhớ đệm mô-đun riêng, có chức năng tích hợp sẵn cho việc này - chỉ cần đảm bảo jest.resetModuleschạy, ví dụ. sau mỗi bài kiểm tra của bạn:

afterEach( function() {
  jest.resetModules();
});

Tìm thấy điều này sau khi cố gắng sử dụng decache như một câu trả lời khác được đề xuất. Cảm ơn Anthony Garvan .

Chức năng tài liệu ở đây .


1
Cảm ơn bạn rất nhiều vì lưu ý này!
mjgpy3

2
Chúa đã thử nghiệm bao lâu trước khi tôi tìm thấy điều này .... cảm ơn bạn!
Tiago

16

Các giải pháp là sử dụng:

delete require.cache[require.resolve(<path of your script>)]

Tìm ở đây một số giải thích cơ bản cho những người, như tôi, là một chút mới trong này:

Giả sử bạn có một example.jstệp giả trong thư mục gốc của thư mục của bạn:

exports.message = "hi";
exports.say = function () {
  console.log(message);
}

Sau đó, bạn require()thích điều này:

$ node
> require('./example.js')
{ message: 'hi', say: [Function] }

Nếu sau đó bạn thêm một dòng như thế này vào example.js:

exports.message = "hi";
exports.say = function () {
  console.log(message);
}

exports.farewell = "bye!";      // this line is added later on

Và tiếp tục trong giao diện điều khiển, mô-đun không được cập nhật:

> require('./example.js')
{ message: 'hi', say: [Function] }

Đó là khi bạn có thể sử dụng được delete require.cache[require.resolve()]chỉ định trong câu trả lời của luff :

> delete require.cache[require.resolve('./example.js')]
true
> require('./example.js')
{ message: 'hi', say: [Function], farewell: 'bye!' }

Vì vậy, bộ đệm được làm sạch và require()chụp lại nội dung của tệp, tải tất cả các giá trị hiện tại.


IMHO Đây là câu trả lời thích hợp nhất
Piyush Katariya

5

tua lại là tuyệt vời cho trường hợp sử dụng này, bạn nhận được một phiên bản mới với mỗi cuộc gọi. Dễ dàng phụ thuộc tiêm để kiểm tra đơn vị node.js.

tua lại thêm một trình thiết lập đặc biệt và getter cho các mô-đun để bạn có thể sửa đổi hành vi của chúng để kiểm tra đơn vị tốt hơn. Bạn có thể

tiêm giả cho các mô-đun hoặc toàn cầu khác như rò rỉ các biến riêng tư ghi đè các biến trong mô-đun. tua lại không tải tập tin và đánh giá nội dung để mô phỏng cơ chế yêu cầu của nút. Trong thực tế, nó sử dụng yêu cầu riêng của nút để tải mô-đun. Do đó, mô-đun của bạn hoạt động giống hệt nhau trong môi trường thử nghiệm của bạn như trong các trường hợp thông thường (ngoại trừ sửa đổi của bạn).

Tin tốt cho tất cả những người nghiện caffeine: tua lại cũng hoạt động với Coffee-Script. Lưu ý rằng trong trường hợp này CoffeeScript cần được liệt kê trong devDependencies của bạn.


4

Tôi muốn thêm vào câu trả lời của luff thêm một dòng và thay đổi tên tham số:

function requireCached(_module){
    var l = module.children.length;
    for (var i = 0; i < l; i++)
    {
        if (module.children[i].id === require.resolve(_module))
        {
            module.children.splice(i, 1);
            break;
        }
    }
    delete require.cache[require.resolve(_module)];
    return require(_module)
}

Vì vậy, điều này là để làm cho các chức năng làm việc trong mô hình con? Đẹp! Một cách ngắn hơn để loại bỏ mô-đun khỏi mảng module.children là sử dụng hàm lọc: module.children = module.children.filter (function (child) {return child.id! == allow.resolve (_module);}) ;
luff

4

Có, bạn có thể làm mất hiệu lực bộ nhớ cache.

Bộ đệm được lưu trữ trong một đối tượng có tên là request.cache mà bạn có thể truy cập trực tiếp theo tên tệp (ví dụ: /projects/app/home/index.jstrái ngược với cách ./homebạn sẽ sử dụng trong require('./home')câu lệnh).

delete require.cache['/projects/app/home/index.js'];

Nhóm của chúng tôi đã tìm thấy các mô-đun sau đây hữu ích. Để vô hiệu hóa các nhóm mô-đun nhất định.

https://www.npmjs.com/package/node-resource


3

Tôi không thể thêm mã vào bình luận trong câu trả lời. Nhưng tôi sẽ sử dụng câu trả lời của @Ben Barkay sau đó thêm nó vào require.uncachehàm.

    // see https://github.com/joyent/node/issues/8266
    // use in it in @Ben Barkay's require.uncache function or along with it. whatever
    Object.keys(module.constructor._pathCache).forEach(function(cacheKey) {
        if ( cacheKey.indexOf(moduleName) > -1 ) {
            delete module.constructor._pathCache[ cacheKey ];
        }
    }); 

Giả sử bạn đã yêu cầu một mô-đun, sau đó gỡ cài đặt nó, sau đó cài đặt lại cùng một mô-đun nhưng sử dụng một phiên bản khác có tập lệnh chính khác trong gói.json, yêu cầu tiếp theo sẽ không thành công vì tập lệnh chính đó không tồn tại vì nó được lưu trong bộ nhớ cache Module._pathCache


3

Tôi không chắc chắn 100% ý của bạn là 'không hợp lệ', nhưng bạn có thể thêm các câu sau vào phần trên requiređể xóa bộ đệm:

Object.keys(require.cache).forEach(function(key) { delete require.cache[key] })

Lấy từ nhận xét của @ Dancrumb tại đây


2

requireUncached với đường dẫn tương đối:

const requireUncached = require => module => {
  delete require.cache[require.resolve(module)];
  return require(module);
};

module.exports = requireUncached;

gọi requestUncached với đường dẫn tương đối:

const requireUncached = require('../helpers/require_uncached')(require);
const myModule = requireUncached('./myModule');

1

Sau hai bước thủ tục là làm việc hoàn hảo cho tôi.

Sau khi thay đổi Modeltệp tức là 'mymodule.js'động, trước tiên bạn cần xóa mô hình được biên dịch sẵn trong mô hình mongoose sau đó tải lại bằng cách sử dụng yêu cầu tải lại

Example:
        // Delete mongoose model
        delete mongoose.connection.models[thisObject.singular('mymodule')]

        // Reload model
        var reload = require('require-reload')(require);
        var entityModel = reload('./mymodule.js');

0

Nếu đó là để kiểm tra đơn vị, một công cụ tốt khác để sử dụng là proxyquire . Mỗi khi bạn truy vấn proxy, nó sẽ làm mất hiệu lực bộ đệm của mô-đun và bộ đệm mới. Nó cũng cho phép bạn sửa đổi các mô-đun theo yêu cầu của tệp mà bạn đang kiểm tra.


0

Tôi đã tạo một mô-đun nhỏ để xóa mô-đun khỏi bộ đệm sau khi tải. Điều này buộc phải đánh giá lại mô-đun vào lần tiếp theo. Xem https://github.com/bahmutov/require-and-forget

// random.js
module.exports = Math.random()
const forget = require('require-and-forget')
const r1 = forget('./random')
const r2 = forget('./random')
// r1 and r2 will be different
// "random.js" will not be stored in the require.cache

PS: bạn cũng có thể đặt "tự hủy" vào chính mô-đun. Xem https://github.com/bahmutov/unload-me

PSS: yêu cầu nhiều thủ thuật hơn với Node trong https://glebbahmutov.com/blog/hacking-node-require/ của tôi

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.