function isPromise(obj) {
return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';
} function isPromise(obj) {
return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';
}A function like this should be a package. Or, really, part of standard js, maybe.
A) The problem it solves is real. It's dumb, but JS has tons of dumb stuff, so that changes nothing. Sometimes you want to know "is this thing a promise", and that's not trivial (for reasons).
B) The problem it solves is not straightforward. If you Google around you'll get people saying "Anything with a .then is a promise' or other different ways of testing it. The code being convoluted shows that.
Should this problem be solved elsewhere? Sure, again, JavaScript is bad and no one's on the other side of that argument, but it's what we have. Is "just copy paste a wrong answer from SO and end up with 50 different functions in your codebase to check something" like other languages that make package management hard so much better ? I don't think so.
Typescript partially solves that by declaring types, but if you have a any variable, you still need to do some probing to be able to safely convert it to a PromiseLike, because TypeScript goes to great lengths to not actually produce physical code on its output, attempting to be just a type checker.
Perhaps if TS or an extension allowed "materializing" TS, that is `value instanceof SomeInterface` generated code to check for the existence of appropriate interface members, this could be avoided, but alas, this is not the case.
I think that module over complicates it as it is, and most people don't need that level of complication in their code.
It's not perfect and a bit of a bolt-on, but io.ts works reasonably well in this area:
It's a part of node, at least: https://nodejs.org/docs/latest-v12.x/api/util.html#util_util...
It's one thing if you own the entire codebase, but if you're building a popular, multiple-years-old library/framework, you can't make the same assumptions.
function isPromise(obj) {
return typeof obj?.then === 'function'
} x instanceof Promise
It works for standard promises, sure there are non standard promises, ancient stuff, that to me shouldn't be used (and a library that uses them should be avoided). So why you need that code in the first place?Also that isPromise function will not work with TypeScript, imagine you have a function that takes something that can be a promise or not (and this is also bad design in the first place), but then you want to check if the argument is a Promise, sure with `instanceof` the compiler knows that you are doing, otherwise not.
Also, look at the repo, a ton of files for a 1 line function? Really? You take less time to write that function yourself than to include that library. But you shouldn't have to write that function in the first place.
2) > sure there are non standard promises, ancient stuff, that to me shouldn't be used
If you're building a library, or maintaining one that's been built over many years, you can't easily make calls like that.
Well, you can, and in the JS ecosystem you'll often find cases where there are two libraries (or two broad classes of libraries) for a certain function that make different choices, one of which makes the simple, modern choice that doesn't support legacy, and one that does the complex, messy thing necessary to deal with legacy code, and which you use depends on your project and it's other constraints.
https://www.typescriptlang.org/docs/handbook/advanced-types....
Make an iframe.
In the iframe:
> window.p = new Promise(() => {});
From the parent window: > window.frames[0].p instanceof Promise
false
Congrats! Your isPromise function was given a Promise and returned the incorrect result. The library returns the correct result. Try again![0] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
`obj instanceof Promise` and `typeof obj.then === 'function'` (is-promise) are much different checks. Frankly, I don't think either belongs in a library. You should just write that code yourself and ponder the trade-offs. Do you really just want to check if an object has a then() method or do you want to check its prototype chain?
This literally is an XY problem: "I need to do A but it's giving me bad results, what do I need to add?" - "Don't use A, it's bad practice. Use B instead and keep using built-in tools instead of hacking something together" In this case use instanceof instead of is-promise because it's a hack around the actual problem of getting objects out of a different context that was explicitly designed to behave this way.
I'm afraid that you don't know what an XY problem is.
JavaScript developers always seem to think they are the smart ones after their 6 weeks of some random bootcamp and then you end up with some crap like NPM where a single line in a package out of hundreds maintained by amateurs can break everybody's development environment.
And it was given a Promise. You just shouldn't use instanceof in multi-window contexts in JavaScript. This is why built-ins like `Array.isArray` exist and should be used instead of `arr instanceof Array`. Maybe you'd prefer to write to TC39 and tell them that `Array.isArray` is wrong and should return false for arrays from other contexts?
There's no use jumping through hoops to avoid admitting that OP made an error. They were wrong and didn't think of this.
const isFalsy = require("is-falsy");
const isObject = require("is-object");
const isFunction = require( "is-function" );
const hasThen = require( "has-then" );
function isPromise(obj) {
return !isFalsy(obj) && ( isObject(obj) || isFunction(obj) ) && hasThen( obj );
}
Just because the code line is more than 50 characters, doesn't mean that we need a new library for that.I'm just saying it's neither necessary nor consistent with the standard for when a library is a good idea proposed upthread, so suggesting it as part of an attempt at reductio as absurdam on that standard is misplaced.
if a tree falls in the woods...?
as of 2020-04-26T00:39+00:00
isFalse would be != isObject would use typeOf isFucntion would use typeOf
Where a library becomes helpful is when you have:
* A real problem (none of those are real problems, and the npm packages for them are essentially unused jokes)
* A solution that is not intuitive, or has a sharp edge, or requires non-obvious knowledge, or does not have a preexisting std approach
Checking for a promise, given the constraints of having multiple types of promises out in the world, falls into both of those. Checking if something is falsey, when Javascript provides !, does not fall into either.
One-liners without dependencies like this should live as a function in a utility file. If justification is needed, there should be a comment with a link to this package's repo.
If you use the same one liners in more than one project and you copy that utility file over, the line gets even fuzzier.
Because the implementation detail of is-promise actually is important. It just checks if an object has a .then() method. So if you use it, it's just as important that you know the limitation.
Not everything needs to be swept under the rug.
When you cribbed the code you should have completely understood what exactly the package was doing, and why, and known what issues it would have had. Since it's a one-liner, it is transparent. Since it is without dependencies, it is unlikely to fail on old code. So it's unlikely to have existing issues and unlikely to develop new issues.
Of course, if you end up using new features of the language in your code, it may fail on that, but the risk old stuff failing should have already been factored in when you decided to upgrade. In fact, the one-liner solves this better since you decide the pace of adaptation of your one-liner to the new features, not the package maintainer.
In properly designed languages, values have either a known concrete type, or the interfaces that they have to support are listed, and the compiler checks them.
Even in JavaScript/TypeScript, if you are using this, you or a library you are using are doing it wrong, since you should know whether a value is a promise or not when writing code.
Doesn't excuse the JS ecosystem and JS as a whole, which truly is a mess. But there's a history behind these things.
If your API works with promises, call .then() on what is handed to you. That's it. Don't make up emergent, untestable behavior on the spot.
ISTM that a framework may need to test for promiseness if it calls promises and functions differently, but it can and should be done as a utility in the framework, not as a separate package.
It’s possible to just treat everything as a promise by wrapping results in Promise.resolve() but that can have performance implications that some franeworks might want to avoid by only going down the promise route when they have to.
For promise implementations, If the callback to then() returns a promise, the promise implementation detects that and resolves that promise behind the scenes: http://www.mattgreer.org/articles/promises-in-wicked-detail/...
I think this would be the solution. I feel like a lot of the NPM transitive dependency explosion just comes from the fact that JavaScript is a language with a ton of warts and a lack of solid built-ins compared to e.g. Python. Python also has packages and dependencies, but the full list of dependencies used by a REST service I run in production (including a web framework and ORM) is a million times smaller than any package-lock.json I've seen.
I think the effort that goes into all the JS syntax and module changes would be better put into developing a solid standard library first.
At the very least, W3, or Mozilla Foundation, or something with some kind of quasi-authority should release a "JS STD" package that contains a whole bunch of helper functions like this. Or maybe a "JS Extras" package, and as function usage is tracked across the eco-system, the most popular/important stuff is considered for addition into the JS standard itself.
Having hundreds of packages that each contain one line functions, simply means that there are hundreds of vectors by which large projects can break. And those can in turn break other projects, etc.
The reason, cynically, that these all exist as separate packages, is because the person who started this fiasco wanted to put a high a download count as possible on his resume for packages he maintains. Splitting everything up into multiple packages means extra-cred for doing OSS work. Completely stupid, and I'm annoyed nobody has stepped up with a replacement for all this yet.
At work, our big webapp depended at some point indirectly on "isobject" "isobj" and "is-object", which were all one liners (some of them even had dependencies themselves!!). Please let's all just depend on lodash and it will actually eventually reduce space and bandwith usage.
See Promise.resolve https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
And is there a reason to use '!!' inside a conditional? Wouldn't obj&& do basically the same thing?
!!null && false === false
null && false === nullThe predictable explosion in dependency trees has caused the predictable problems like this one. I feel I much prefer the C/C++ way of "modules are easy to make, but difficult to use".
class World { then () { return 0; } } isPromise(new World) // true
If there really isn't a safe and better way to tell if an object is an instance of Promise…then color me impressed.
const p = {then: () => 0}Promise.resolve({then: () => console.log('called')})
Promises autoflatten since you can't have Promise<Promise<T>>, so you'll see that this code prints 'called'.
https://doc.rust-lang.org/std/primitive.bool.html#method.the...
https://doc.rust-lang.org/std/cmp/enum.Ordering.html#method....
If a small standard library is using it for things that aren't promises, you can bet your ass that there are javascript libraries using it for things that aren't promises.
Like I said, I just chose to look at rust first because it's documentation has a good search bar.
That's why the next time I run into such a method, that doesn't belong to a promise and behave the way a promise's "then" method does, will be the first time I can remember, despite having worked primarily or exclusively in Javascript since well before promises even existed.
I'm sure there is an example somewhere on NPM of a wildcat "then", and that if you waste enough of your time you can find it. So what, though? People violate Rust idioms too from time to time, I'm sure. I doubt you'd argue that that calls Rust idioms themselves into question. Why does it do so with Javascript?
People bounce around between languages, especially to javascript. An expert javascript dev might not call things "then" but the many dabblers might. Going back to the original point this is a footgun, not only an avenue for malicious code to cause trouble.
My primary point is just that you are mistaken when claiming that this bug could only be surfaced by malicious code.
My secondary (somewhat implicit) point is that having an "is-promise" function is a mistake when there is no way to tell if something actually is or is not a promise. This library/function name is lying to the programmers using it about what it is actually capable of, and that's likely to create bugs.
Absent that evolved level of tooling, and especially in an environment still dealing with the legacy of slow standardization and competing implementations that I mentioned in another comment, you're stuck with best effort no matter what. In the case of JS and promises, because of the norm I described earlier in this thread, best effort is easily good enough to be going on with. It's not ideal, but what in engineering practice ever is?
With javascript promises in particular, the duck typing suffers from this unfortunate fact that you can't easily check if something can be awaited-upon or not. I don't think I really care if something is a promise, so long as I can do everything I want to to it. So I view the issues here as this function over-claiming what it can do, the limitation on the typesystem preventing us from checking the await-ability of an object, and the lack of static type checking. None of those are necessitated by duck typing.
I disagree that you're stuck with this best-effort function. It's perfectly possible to architect the system so you never need to query whether or not an object is a promise. Given the lack of ability to accurately answer that question, it seems like the correct thing to do. At the very least I'd prefer if this function was called "looks-vaguely-like-a-promise" instead of "is-promise".
I don't care enough to go dig that out on a Saturday afternoon, but I suspect that if I did, we'd end up agreeing that whoever is using it could, by dint of sufficient effort, have found a better way.
On the other hand, this appears to be the first time it's been a significant problem, and that only for the space of a few hours, none of which were business hours. That's a chance I'd be willing to take - did take, I suppose, in the sense that my team's primary product is built on CRA - because I'm an engineer, not a scientist, and my remit is thus to produce not something that's theoretically correct in all circumstances, but instead something that's exactly as solid as it has to be to get the job done, and no more. Not that this isn't, in the Javascript world as in any other, sometimes much akin to JWZ's "trying to make a bookshelf out of mashed potatoes". But hey, you know what? If the client only asks for a bookshelf that lasts for a minute, and the mashed potatoes are good enough for that, then I'll break open a box of Idaho™ Brand I Can't Believe It's Not Real Promises and get to work.
I grant this is not a situation that everyone finds satisfactory, nor should they; the untrammeled desire for perfection, given sufficient capacity on the part of its possessor and sufficient scope for them to execute on their visions, is exactly what produces tools like Typescript, that make it easier for workaday engineers like yours truly to more closely approach perfection, within budget, than we otherwise could. There's value in that. But there's value in "good enough", too.
At the same time, it is intensely wild to me that their "then" is an alternative to their "should", which apparently just re-executes the callback it's given until that callback stops throwing. If your tests require to be re-run an arbitrary and varying number of times in order to pass, you have problems that need to be dealt with in some better way than having your test harness paper over them automatically for you.
(async () => ({
then() {
console.log("Called")
}
}))()