I want to edit my JSON file using NodeJS, everything work exept the last write, my JSON file get }
at the end.
My code :
function editPackState(request, response) { var name = request.params.name, state = parseInt(request.params.state, 10); fs.readFile('./configs/packs.json', 'utf8', function(err, data) { var packs = JSON.parse(data), findedPack = _.find(packs.packs, { name: name }); findedPack.state = (state) ? true : false; fs.writeFile('./configs/packs.json', JSON.stringify(packs, null, 4), function() { disableAllModules().then(function() { activateModules(findedPack.modules).then(function() { response.redirect('/_gestiastore'); }); }); }); }); } async function disableAllModules() { fs.readFile('./configs/modules.json', 'utf8', function(err, data) { var modules = JSON.parse(data); _.forEach(modules.modules, function(module) { module.state = false; }); fs.writeFile('./configs/modules.json', JSON.stringify(modules, null, 4), function() { return Promise.resolve(); }); }); } async function activateModules(moduleNames) { fs.readFile('./configs/modules.json', 'utf8', function(err, data) { var modules = JSON.parse(data); _.forEach(modules.modules, function(module) { if (moduleNames.includes(module.name)) { module.state = true; } else { module.state = false; } }); fs.writeFile('./configs/modules.json', JSON.stringify(modules, null, 4), function() { return Promise.resolve(); }); }); }
packs.json
:
{ "packs": [ { "name": "Vitrine", "state": false, "modules": [ "cms" ] } ] }
modules.json
:
{ "modules": [ { "name": "blog", "state": false, "models": [ "ArticleModel", "ArticleCategoryModel" ] }, { "name": "portfolio", "state": false, "models": [ "ProjectModel", "ProjectCategoryModel" ] }, { "name": "cms", "state": false, "models": [ "PageModel", "LinkModel", "SectionModel", "SubSectionModel", "FileModel" ] }, { "name": "mentions", "state": false, "model": [] }, { "name": "vehicle", "state": false, "models": [ "VehicleModel", "VehicleBrandModel", "VehiclePhotoModel", "VehicleTypeModel" ] }, { "name": "contact", "state": false, "models": [] }, { "name": "calendar", "state": false, "models": [ "EventModel", "EventCategoryModel" ] }, { "name": "ecommerce", "state": false, "models": [ "ProductModel", "ProductBrandModel", "ProductCategoryModel", "ProductSupplierModel", "ProductVariationModel", "OrderModel", "OrderProductModel" ] }, { "name": "emailing", "state": false, "models": [] }, { "name": "analytic", "state": false, "models": [] }, { "name": "testimonial", "state": false, "models": [ "TestimonialModel" ] }, { "name": "gallery", "state": false, "models": [ "GalleryModel", "GalleryPhotoModel" ] }, { "name": "newsletter", "state": false, "models": [ "NewsletterModel" ] }, { "name": "documentation", "state": false, "models": [ "DocumentationModel" ] }, { "name": "rgpd", "state": false, "models": [] }, { "name": "joboffer", "state": false, "models": [ "JobOfferModel" ] }, { "name": "coordinate", "state": false, "models": [ "CoordinateModel" ] }, { "name": "contributor", "state": false, "models": [ "ContributorModel" ] }, { "name": "form", "state": false, "models": [ "FormModel" ] }, { "name": "cgv", "state": false }, { "name": "cashregister", "state": false }, { "name": "invoice", "state": false }, { "name": "resacartabo", "state": false } ] }
Result of modules.json
:
{ "modules": [ { "name": "blog", "state": false, "models": [ "ArticleModel", "ArticleCategoryModel" ] }, { "name": "portfolio", "state": false, "models": [ "ProjectModel", "ProjectCategoryModel" ] }, { "name": "cms", "state": false, "models": [ "PageModel", "LinkModel", "SectionModel", "SubSectionModel", "FileModel" ] }, { "name": "mentions", "state": false, "model": [] }, { "name": "vehicle", "state": false, "models": [ "VehicleModel", "VehicleBrandModel", "VehiclePhotoModel", "VehicleTypeModel" ] }, { "name": "contact", "state": false, "models": [] }, { "name": "calendar", "state": false, "models": [ "EventModel", "EventCategoryModel" ] }, { "name": "ecommerce", "state": false, "models": [ "ProductModel", "ProductBrandModel", "ProductCategoryModel", "ProductSupplierModel", "ProductVariationModel", "OrderModel", "OrderProductModel" ] }, { "name": "emailing", "state": false, "models": [] }, { "name": "analytic", "state": false, "models": [] }, { "name": "testimonial", "state": false, "models": [ "TestimonialModel" ] }, { "name": "gallery", "state": false, "models": [ "GalleryModel", "GalleryPhotoModel" ] }, { "name": "newsletter", "state": false, "models": [ "NewsletterModel" ] }, { "name": "documentation", "state": false, "models": [ "DocumentationModel" ] }, { "name": "rgpd", "state": false, "models": [] }, { "name": "joboffer", "state": false, "models": [ "JobOfferModel" ] }, { "name": "coordinate", "state": false, "models": [ "CoordinateModel" ] }, { "name": "contributor", "state": false, "models": [ "ContributorModel" ] }, { "name": "form", "state": false, "models": [ "FormModel" ] }, { "name": "cgv", "state": false }, { "name": "cashregister", "state": false }, { "name": "invoice", "state": false }, { "name": "resacartabo", "state": false } ] }} // the second "}" is too much
Advertisement
Answer
Your disableAllModules
and activateModules
functions return before their respective readFile
callback functions are evaluated.
As a result, when you do this:
disableAllModules().then(function() { activateModules(findedPack.modules).then(function() { response.redirect('/_gestiastore'); }); });
The following happens:
disableAllModules()
is called, returnsundefined
, which is wrapped in a Promise due to theasync
keyword being used- The
fs.readFile
operation starts - The
then
callback chained ondisableAllModules()
is run, callingactivateModules(…)
- There is now a big race between reading the file in
disableAllModules
, reading the file inactivateModules
, writing to the file indisableAllModules
, and writing to the file inactivateModules
As the Node documentation points out:
It is unsafe to use fs.writeFile() multiple times on the same file without waiting for the callback.
This suggests that you can have competing writes to the same file, resulting in corrupted data like you are seeing here. It’s likely only because you are making small modifications that the data is not more significantly broken.
I recommend refactoring to follow this approach:
- Read the file
- Make all modifications to the data in memory
- Write the new data a single time
Provided this script is run infrequently (or at least, never concurrently), this is a simple approach that should avoid many of the pitfalls of concurrency that you are encountering. If it is possible for multiple instances of this script to be running concurrently, you will need a locking mechanism to avoid separate processes writing to the file concurrently (e.g. lockfile).