Uh oh!
There was an error while loading. Please reload this page.
fs: un-deprecate existsSync(), throw error on EPERM - #4077
Conversation
fs.existSync() is actually a pretty useful convenience function, “fix” its technical shortcoming by throwing on EPERM errors so we can un-deprecate it.
cjihrig
commented
Nov 30, 2015
I'm in favor of keeping I'm not in favor of having it throw, because at that point you could just use |
Fishrock123
commented
Nov 30, 2015
You can't though. You can't "just use accessSync()" if the code happens to be run on windows, that's awful.
I think the current behavior is actually worse. |
targos
commented
Nov 30, 2015
I'm not sure I understand what is the difference with the |
cjihrig
commented
Nov 30, 2015
Isn't one of the biggest gripes about getting rid of |
Fishrock123
commented
Nov 30, 2015
No, it's that the others require excess options and stuff. Ideally, |
Fishrock123
commented
Nov 30, 2015
@targos hmmm, I'm not really sure? As far as I can tell you can have an EPERM both if the file does and doesn't exist, is that also the case for EACCESS? The only difference then would be I don't think you can fix an EPERM from within the node apis. |
cjihrig
commented
Dec 1, 2015
What excessive options? |
targos
commented
Dec 1, 2015
Take this example: For the last call, the |
Fishrock123
commented
Dec 1, 2015
Sorry, It's that for Maybe this isn't really an issue, idk. |
ChALkeR
commented
Dec 1, 2015
Adding a thow in a place that wasn't throwing before is a |
ChALkeR
commented
Dec 1, 2015
It definitely is, and not on Windows-only. There are more cases where you can't be sure if a file exists or not, the most simple of them being: >fs.statSync('/root/x')
Error: EACCES: permissiondenied,stat'/root/x'The above result is equivalent in case when There are also other cases when you can't be sure if the file exists or not, for example remote mounts, fuse-whatever, etc. |
ChALkeR
commented
Dec 1, 2015
To correspond with it's name, the function fs.existsSync=function(path){nullCheck(path);// No need to catch invalid paths it if we start throwing errorstry{binding.stat(pathModule._makeLong(path));returntrue;}catch(e){if(e.code!=='ENOENT')throwe;// ENOENT — the file doesn't exist. Everything else — who knows?returnfalse;}};with a corresponding But that would break existng code very badly. |
saghul
commented
Dec 1, 2015
FWIW, I'm -1 on the change. If there is anything we can improve in the libuv layer (we emulate access(2) on Windows after all) in order to behave more consistently with Unix, give a shout at libuv/libuv. |
jasnell
commented
Dec 1, 2015
I'm generally +-0 on this change but willing to defer to @saghul and @bnoordhuis' judgement on it. |
Fishrock123
commented
Dec 1, 2015
Is anyone positive for undeprecating it and leaving it;s functionality? Should |
saghul
commented
Dec 1, 2015
@Fishrock123 see the example here #4077 (comment) There is no data to say it exists or it doesn't. |
Fishrock123
commented
Dec 1, 2015
Which is why I suggested for it to throw, because neither answer is correct. On second thought, I guess leaving it alone otherwise is fine-ish. |
saghul
commented
Dec 1, 2015
If existsSync is going to throw then what's the advantage over accessSync? I don't think there is a silver bullet here. Some say that are only interested in knowing if a file exists, but we can't really give an answer to that if the containing directory doesn't have the right permissions. |
domenic
commented
Dec 1, 2015
Exactly. This is why I'm in favor of the status quo, of keeping it deprecated. If the main complaint is really about having to use try/catch, we could consider introducing a We could also explain the above in the documentation for why it is deprecated. |
sam-github
commented
Dec 1, 2015
Please don't change the API behaviour and keep the same name. Right now, I can write portable code by looking for access, and if its not there, I can use exists. If there are two versions of exists, then I'll actually need to include node-semver, and check the node version to see what its API is, or perhaps construct some more complex code. Though, frankly, I'm sometimes just using neither (edit: neither exists or access), replacing them with stat, which works on all node versions, and my CLIs usually only want rough checks for existence, such as: use the first dir that exists of A or B to store the data files. I'm OK with deprecating and then deleting APIs we don't like, or that don't match node convention. It makes node better in the long run. If we need something slightly different from access, because access is insufficiently like exists on some platforms, then I suggest proposing a new fs API to do that, with a new name. |
trevnorris
commented
Dec 1, 2015
I'm +1 for @domenic's API concept. More explicit and predictable. There's been a lot of argument over what "exists" means, while it is much easier to define "accessible". |
Fishrock123
commented
Dec 1, 2015
Proposal
Question: Would we accept that in core? |
domenic
commented
Dec 1, 2015
Does it really solve people's problems? My understanding is they want a boolean return value. |
Fishrock123
commented
Dec 1, 2015
I still don't really think that's adequate, but this can be used in a boolean check if needed, or check the error if desired. |
trevnorris
commented
Dec 1, 2015
I think the point of the access check is, is it accessible or isn't it. don't see what an error would need to convey. |
Fishrock123
commented
Dec 1, 2015
Hmmm, thinking about it again, I don't think people care that much about accessible, but more if a file exists. I.e. Does any config exist yet, or should we make a new one? If it exists there, but isn't accessible, you can't say it doesn't exist or the program will probably break. |
bnoordhuis
commented
Dec 1, 2015
Bad example. In that case you'd open the config file with Or maybe it's a great example of why |
amb26
commented
Dec 9, 2015
+1 for undeprecating existsSync. I am interested in the intention, expressed via the presence of a file in the filesystem, that it should be processed - summarised as a simple boolean that doesn't require me to treat its presence or absence as "exceptional". I think we're all familiar with the recommendation not to use exceptions for normal program control flow. Whether later FS operations on the file end up throwing, as a result of a permissions/access failure, or a race condition that removed it in the intervening time, is absolutely fine with me - that condition then is exceptional. If exists/existsSync throws early if it can detect one of those exceptional conditions I really don't mind. Also whether it returns a genuine boolean or simply a truthy/falsy value I also really don't mind. fs.accessErrorSync solves a problem I don't have. I, and I think thousands of others, feel that there's absolutely nothing problematic with the definition of whether a file "exists" or not. I think we are perfectly happy with the fact that the mere existence of a file at a particular point in time provides no guarantees that anything can actually be done with it at a later point in time. There's no need to confuse/couple these two requirements together. |
saghul
commented
Dec 9, 2015
@amb26 repeating myself: we can't give a straight answer to whether a file exists or not if its inside a directory which we cannot enter. Example: /root/.ssh/id_rsa (your user does not have the rights to access /root, but the file exists) What should existsSync return? It does exist, but we cannot know that. |
amb26
commented
Dec 9, 2015
@saghul - I'm perfectly happy with an exception as the answer to that |
matthew-dean
commented
Dec 9, 2015
Yes, that's correct. A thrown exception defeats the purpose.
This argument has been repeatedly both stated and defeated in the referenced thread. It's an overly obsessive and fixated answer, and seems to be a base argument for the original deprecation. You are technically correct, yet these edge cases are not valid justification for throwing the baby out with the bathwater. It's "We can't be sure of the answer, therefore we'll tell you nothing, and in fact, not allow you to ask." Think about the logic you're applying to this function applied to restaurant dining. "Do you still have the fish special?" The fact that the fish special cannot be PROVEN to not exist does not matter. Refusing to say yes or no based on current available data is not MORE useful than returning boolean true / false. No scientist says that they can't reach a conclusion since it's technically true that cause and effect can never be proven, or it's possible that the entire world is a simulation in the scientist's mind, therefore they should assert nothing. It's technically TRUE, but that doesn't mean that technical trueness is always deterministic as to what information is USEFUL, or if those technically true things are really all that relevant. That said, in the world of JavaScript, were it the case that file access is the issue (if that can be determined), then it seems perfectly legitimate, as someone else suggested, to return a "falsy" value such as undefined, such that a boolean test will still tell you that the file cannot be found. And, in the event that someone needs to care about the difference between "exists" and "not found", there are other functions that CAN throw error values which can be used to split out different behavior. |
crrobinson14
commented
Dec 9, 2015
❤️ I could not agree more - this is a perfect analogy. I guess if I was going to recap my arguments from #1592 without ranting on and on, it would be just to say "it's not Node's job to fix this particular problem, either for operating system devs or application devs." The only issue the philosophical position resolves (the race condition) is rare, not generally a problem with the use-cases described at length, and present in every other language that provides the same metaphors. Document it and move on. |
dfabulich
commented
Dec 9, 2015
@domenic wrote:
See pull request #4217 which adds an @domenic, if you really think that's a good idea, please merge it. But I think we shouldn't merge it; we should merge this PR #4077 instead. Everything wrong with #4077 is also wrong with #4217 as well. Race conditions? Check. Potential for misuse? Check. Easy to use? Check. But #4217 clutters the API with a function that's redundant with |
trevnorris
commented
Dec 9, 2015
@dfabulich The argument for adding a function like |
dfabulich
commented
Dec 9, 2015
@trevnorris I've read all the threads on this. I get it. That's why this PR #4077 adds the "throw error on EPERM" stuff. And that's also why I support merging it. (I agree with those who say that it would require a semver bump. To me, that sounds like a great addition to Node 6.) #4077 is our best bet. My PR #4217 is an acceptable second-best option. |
bjouhier
commented
Dec 10, 2015
OK, there are plenty of edge cases where In our application we use I think that a large part of the disagreement comes from a difference of perspective between library developers and application developers:
You are trying to prevent library implementers from shooting themselves in the foot. Fine. But don't forget the application developers. For them backward compatibility may be more important. It is important that their code, and their dependencies, which might not be all new and shiny, don't break because of a missing API. |
trevnorris
commented
Dec 10, 2015
@dfabulich The "throw in EPERM" is why |
crrobinson14
commented
Dec 10, 2015
@trevnorris I don't understand that position. The "other" core dev position was "just try to open the file, and try/catch that." How is try/catch a problem here but a recommended answer there? I don't understand all these protectionist positions. Bad developers will write bad code - in any language. I've found security holes in Erlang projects, Ruby projects, Scala projects, C# projects... Every one of them was suppose to be "more secure than that awful PHP." Sooner or later it's not the language's job to fix everything a human might do. @bnoordhuis in PR #4217 said basically "you can't have it because it's a TOCTOU pathway and you can't be trusted with this awesome responsibility." Are you going to stop allowing people to run Node as root, too? That's WAY more dangerous, and not discussed at all. There's a huge pile of stuff in NodeJS that throws. They're well documented and understood, and the developer's fault if they don't catch it. EPERM here is a LITTLE weird to me... but WAY less weird than deprecating existsSync in the first place. If I just want to know if the file exists, I maintain that it's less clear, less readable, and less maintainable to rely on a side effect of another function to determine that when there was a perfectly good function already that handled it. You're spending a huge amount of time here figuring out how to find alternatives for something people want to do, solely because you're deleting an already functional way that they have today. I can see what's going to happen here. There's going to be a hugely-upvoted Q&A on StackOverflow about this. The most popular answer is going to be a monkey-patch that still has all the same issues as the original. Maybe that's the best answer for now - it's certainly what I'm going to use. I check for file-existence a LOT without immediately wanting to open and read the file. (Which you still have to try/catch.) It's a valid and useful pattern to me, and I have no intention of saddling some future maintainer with an ugly comment that says "This call to accessibleSync doesn't really need access to the file. We're just seeing if it exists, but since the NodeJS devs took away existsSync we're calling this instead. So just ignore the fact that it says accessible..." |
matthew-dean
commented
Dec 10, 2015
Also, as a side note: the original call to deprecate |
trevnorris
commented
Dec 10, 2015
We don't always agree. ;-)
My position isn't about that. It's that the API itself is fundamentally flawed. The point of
The point I'm trying to make is a file "just existing" is ambiguous. Leading to one reason why
Then what does it matter?
The documentation states:
So it achieves for all intents and purposes the same thing but is better defined. With the added bonus of being more performant. |
matthew-dean
commented
Dec 10, 2015
@trevnorris@crrobinson14 I think the better solution would be to preserve if(!fs.existSync('foo.txt')){ ... // Doesn't exist or isn't accessible - backward-compatibleif(fs.existSync('foo.txt')===false){ ... // No access error thrown, but no file foundif(fs.existSync('foo.txt')===undefined){ ... // Access error thrown when trying to determine existenceif(fs.existSync('foo.txt')){ ... // Hey, look, there's a file there! Cool.I think this is a slightly better pattern than just a true/false return for |
crrobinson14
commented
Dec 10, 2015
@trevnorris I get most of what you're saying except this part:
It sounds like you're suggesting or are more readable and better practice than: No reminder to future maintainers about your intentions with the last option. This is what I'm stuck on. It just seems so awkward to me. I feel like I need to clarify my intentions to future devs in every spot I do this because they may not realize at a glance that I'm only checking existence. I do get the try/catch comment. I totally get the |
crrobinson14
commented
Dec 10, 2015
@matthew-dean Actually I'd side with the core devs on that point. My position is that using side effects as primary logic pathways is unclear. Having to I can totally see the value of |
matthew-dean
commented
Dec 10, 2015
@crrobinson14 I can respect that. I'm just trying to complain less and find something closer to an acceptable middle ground. If accessibleSync were to never throw an error, I think that would work for me. OR some other non-throwy method that returned a queryable object, like: if(fs.safeAccessSync('foo.txt').accessible){ ...
// orif(fs.safeAccessSync('foo.txt').exists){ ...
// orif(fs.safeAccessSync('foo.txt').writable){ ...Something that was a safe and concise utility method that could easily return existence (or accessibility, or both) without throwing errors - to me, THAT would be a perfectly fine replacement for (Although, of course, "safeAccessSync" is a horrible name.) |
crrobinson14
commented
Dec 10, 2015
@matthew-dean@trevnorris Just a suggestion, what if you added an than relying on default behavior that does the same, or using |
bjouhier
commented
Dec 10, 2015
I have a proposal:
People who use the old (bad) APIs will be happy: their code will continue to work. You will be happy: people who want the old APIs to stay around won't be polluting your debates. Once the new APIs are out, we'll see. If 2 years down the road a large majority of modules have switched to the new APIs, then it might be time to deprecate the old functions. If, on the other hand, the new API got poor adoption, then the old functions should stay. Let time (and the Darwinian process) sort this out. There is no reason to rush. |
amb26
commented
Dec 10, 2015
@matthew-dean - yes, I agree completely. The point is that existsSync, regardless of its safety/consistency/efficiency, is a compact and easily recognised means of conveying intention. |
matthew-dean
commented
Dec 10, 2015
@crrobinson14 I thought of something like that too. if(fs.accessibleSync(filePath,fs.F_EXISTS)){ ... }And, hey, you know what? If one were being really clever, one could add a bit of sugar to the above method and write: if(fs.existsSync(filePath)){ ... }:P But, look, if it were the first option, I would be fine. |
matthew-dean
commented
Dec 10, 2015
@bjouhier This also seems reasonable. If a clearly better approach DOES manifest, then like I've said, yeah for sure, I would happily say goodbye to |
trevnorris
commented
Dec 10, 2015
It seems the word "access" is leading to incorrect conclusions, like:
Which bring the need to explain the usage of API in context, such as:
With the
On Linux at least if a file can be viewed then it can be potentially accessed. Thus it exists. But this is not true on Windows. Hence why My only goal here is to make sure the API is well defined across platforms. |
dfabulich
commented
Dec 10, 2015
@trevnorris I'd use it. In most scripts, I'd use it without wrapping it. |
Fishrock123
commented
Dec 21, 2015
I don't really know where to go with this, so I'm gona close it. |
This is my conclusion from #1592 (comment).
A significant amount of people find specifically
existsSync()useful (especially in simple CLI apps) and it seems unreasonable to require a module for this. Keepexists()deprecated as it breaks our errback API convention.This commit makes
existsSync()throw onEPERM. The technical problem withexistsSync()(besides a race condition if used badly) is that you cannot know if a file does or doesn't exist under Windows with improper permissions.Throwing in that case really isn't that bad of an idea, since the regular output will probably cause errors anyways down the line, and I don't think these errors can be remedied from Node.js. Instead, error so the end user can clean up their file permissions. Catching this error in user code isn't really worthwhile due to these reasons, so suggest against it.
Discuss. cc @nodejs/collaborators