TE
科技回声
首页24小时热榜最新最佳问答展示工作
GitHubTwitter
首页

科技回声

基于 Next.js 构建的科技新闻平台,提供全球科技新闻和讨论内容。

GitHubTwitter

首页

首页最新最佳问答展示工作

资源链接

HackerNews API原版 HackerNewsNext.js

© 2025 科技回声. 版权所有。

Promises are not neutral enough

202 点作者 giulianoxt超过 7 年前

35 条评论

magnushiie超过 7 年前
I think the author wants promises to represent computation, whereas they represent predetermined (i.e. single-shot) events. He mentioned C# Tasks, which do mainly represent computation, but in some cases Tasks are also used as events and this gets confusing as hell. I&#x27;ve worked with C# Tasks and hope that MS once cleans this up and builds the stuff on promises instead. Note that the C# language construct uses the awaitable pattern (GetAwaiter method) instead of tasks - awaitables are actually pretty similar to promises.<p>1. Eager, not lazy - I think it was a mistake for the promise constructor to take a function, and in that way lead the users to believe the promise represents a computation. Creating a pair of promise and future (the latter as the producer side, like in C++) would be much cleaner. I disagree that lazy would be more general, you can simulate lazyness with functions, but you couldn&#x27;t eliminate the performance cost of creating the unnecessary closure with a lazy solution. Regarding getUserAge - the common case for that function would be to take the user ID as the parameter (and hence would be lazy by construction), the parameterless version is a special case.<p>2. No cancellation - cancellation is much better represented with cancellation tokens (even C# Tasks cancel with cancellation tokens, so does fun-task mentioned at the end, though in non-composable way) - you cannot build a generic solution that can cancel the right computations. With cancellation tokens it&#x27;s clear what cancels what.<p>3. and 4. (as well as being allowed pass non-promises to places where only promises make sense, like Promise.all and await) are unfortunate accidents that make typed environments (e.g. TypeScript) harder to work with but are not that important as 1 and 2.
评论 #16389947 未加载
评论 #16386454 未加载
roguecoder超过 7 年前
This article doesn&#x27;t even touch on the worst sin of JavaScript Promises: they swallow errors and exceptions, which makes them nearly impossible to test correctly and makes debugging horrifying (if you even notice anything is wrong.)<p>Promises are a great example with the problems of believing that something good in one language will be good in another. Promises in JavaScript are fighting the language, because JavaScript is fundamentally a collection of isolated but contextual behaviors.<p>Using Agents to encapsulate callbacks is easier to reason about, easier to test, less likely to swallow errors whole, and doesn&#x27;t have any of the problems laid out in this article. Unfortunately because it isn&#x27;t a model popular in any other language, it doesn&#x27;t have the name recognition Promises do.
评论 #16388189 未加载
评论 #16386785 未加载
_greim_超过 7 年前
I don&#x27;t know. André Staltz is a great programmer, but I can&#x27;t help but think what seems &quot;opinionated&quot; to him about promises boils down to the fact that they don&#x27;t perfectly match certain quasi-ideological preferences he has about async programming, at the expense of all other concerns. As he states at the end, promises still work, you can get things done and everything is fine. But the part about them being opinionated I just can&#x27;t get behind.<p>In fact, if promises worked the way he wanted them to, it would hurt the ecosystem in every category he mentions. Lazy promises would cease representing a single value, and be un-cacheable. Promises that didn&#x27;t flatten inner promises would create endless confusion and ambiguity over &quot;onion-promise&quot; scenarios. Sometimes-synchronous promises would introduce subtle and sometimes catastrophic runtime ambiguities (aka &quot;release zalgo&quot;). Even cancelable promises would raise thorny issues regarding whether promises are intended to be multicast or unicast, which is a problem the current design side-steps entirely.
评论 #16386875 未加载
评论 #16386761 未加载
评论 #16386903 未加载
评论 #16387572 未加载
评论 #16388293 未加载
twohearted超过 7 年前
The analogies damage this article because they feel wrong. For example the &quot;never synchronous&quot; example is more like this:<p><i>You order a burger at the cashier window, then go to the pickup window. If the burger is already made, it&#x27;s already at the pickup window when you get there.</i><p>The author wants a special case where if the burger is already made, they hand it to you immediately at the cashier window. This might seem more efficient, but both in the restaurant and in code it makes logic way more complex.
评论 #16387646 未加载
评论 #16387711 未加载
matharmin超过 7 年前
I work with fairly large JavaScript codebases, and the issues mentioned in the post has never been an issue for me. The switch from callbacks to Promises, and later to async-await has made a massive improvement to the ease of writing, reading and maintaining the code. Lazy and cancellable tasks are edge-cases that don&#x27;t need support in Promises directly. I haven&#x27;t needed either of those in more than a couple of places in the code, versus thousands of places where Promises are used as is.<p>I can see the automatic unwrapping of Promises to be an issue in some libraries that want to make specific guarantees, but in most of my code this has behaviour has simplified things.<p>I definitely prefer having Promises and async-await right now over another theoretically sound (but probably more verbose) system available in a couple of years.
评论 #16387833 未加载
评论 #16388179 未加载
Const-me超过 7 年前
I don&#x27;t think the author has experience working with C# tasks.<p>Technically, the API documentation indeed says a Task has Start() methods i.e. is lazy.<p>But practically, in the majority of cases they are created already in Running or WaitingToRun state. This applies to tasks returned by asynchronous APIs in the framework, tasks implemented by user-written async methods, and tasks started with Task.Run() static methods. Calling Start() on them will throw an exception complaining about the wrong task state. So, in the current versions of .NET, the tasks are eager just like in JS.<p>I think the lazy tasks are mostly for backward-compatibility with older .NET framework 4.0 that already had tasks but didn’t support async-await.
rictic超过 7 年前
Neither lazy nor eager is neutral. Sometimes you want one, sometimes you want the other, and you can build either out of the other.<p>For promise cancellation, this has been talked to death, but in short, making any function preemptable at any point in its execution makes writing correct code much much harder. As an example, I&#x27;ve got an API that takes independently cancellable requests. Multiple requests often need to calculate the same thing, so there&#x27;s a cache. Any given promise in the system might be downstream of multiple requests. If cancellation is built into promises, how do I express how cancellation should propagate through the tree of promises?<p>A C#-style cancellation token API, orthogonal to promises, is simple, easy to build, and easy to understand.
评论 #16386685 未加载
评论 #16387498 未加载
skybrian超过 7 年前
If promises were lazy (basically just function composition), it seems like we&#x27;d have similar problems to Haskell where it&#x27;s difficult to understand performance. You wouldn&#x27;t know when an I&#x2F;O operation starts or whether it will get executed again. Maybe that&#x27;s okay for a high-level API, but low-level I&#x2F;O operations are not idempotent, so this seems risky?<p>So this looks like a trade-off: you could make function composition easier only by making Promises less suitable for their original purpose. By going generic, you lose an important guarantee that a Promise is just a value.
egeozcan超过 7 年前
const myJob = { run: () =&gt; fetch(... is too long? Eager is easy to make lazy. While the opposite is also true, it means a superfluous run. It is worse, semantically speaking. I&#x27;d argue that eager is more general than lazy.<p>Also, cancellation is yet another state and it&#x27;s hard to generalize especially when you don&#x27;t have threads.<p>Promises should always be async because you&#x27;d want the result to be consistent. If I&#x27;m returning a promise and you are depending it to be sync, that means it weakens my flexibility. It makes the code harder to reason about.
Animats超过 7 年前
OK, we can&#x27;t do threaded imperative programming because threads are expensive and people botch the locking. So we have callbacks where completion of some external event calls you back. Then you need closures so the callback has some state so it knows what to do when called back. Now you have a control structure problem, and need a state machine to decide what to do next. But most of the time you just want to do the next thing, so there&#x27;s syntax such as &quot;.then()&quot; so you can write imperative programs again.
paroneayea超过 7 年前
One of the big challenges mentioned with promises is that you have to kind of commit to promises linking to promises... this is a common problem with async systems added later, where you have to &quot;line them up like gears&quot;, and you can&#x27;t just do async functions which call non-async functions which call async functions and expect it to work. Python has this problem too.<p>There&#x27;s a solution in delimited continuations, however delimited continuations seem to only be used and understood in the Scheme community (are they used anywhere else?). Delimited continuations allow you to suspend your code to a &quot;prompt&quot; lower in the stack at that point... and it doesn&#x27;t matter if you have non-&quot;async&quot; code in between.<p>It&#x27;ll be nice when they make their way to other more mainstream languages.
dmitriid超过 7 年前
- Eager is relatively easy to convert to lazy, if needed. The inverse isn’t true.<p>- async is relatively easy to turn into sync. The inverse isn’t true.<p>- no API design is ”neutral”. Any API design is opinionated. Cancelable lazy synchronous promises is just as opinionated a design as the current design.
kazinator超过 7 年前
The whole idea of cancelation is poor; it shouldn&#x27;t even be a feature.<p>The way you avoid unnecessary computation, when you have laziness, is to just roll it into the lazy semantics.<p>Have it so that if the promise generates something complicated, like a sequence, that the promise only generates as much of that something as is accessed (and maybe only a little bit beyond that).<p>In other words, the async promises should perhaps behave not so differently from synchronous lazy mechanisms.<p>The two are flipsides of the same coin. Say I have a synchronous lazy list (of strings). The strings come from reading a file. Ah, but reading a file is asynchronous at the OS level. So actually the list is asynchronous, in a sense. When we access the first element in the list, a line is read from the file. The underlying stream object reads an entire buffer-sized chunk, though: still synchronously. Moreover the OS behaves asynchronously and reads ahead in the file, caching more of it than the stream library asked for. Of course, the OS doesn&#x27;t read the <i>whole file</i> (unless it&#x27;s small). Just a little bit ahead. Enough ahead not to hammer the I&#x2F;O subsystem with lots of small operations.<p>We can create this list over a log file that has 100 million lines, then read just the first 100 lines and stop using it. The underlying stream library might read 16K of the file, of which the 100 lines occupies only the first 8. The OS might have read ahead by quite a bit more than that and cached more of the file, and the hard drive&#x27;s firmware might have buffered an entire track. If we don&#x27;t read anything more from that list, then the operation is effectively canceled. The OS won&#x27;t cache any more from the file; the stream library won&#x27;t buffer more of text stream.
评论 #16388982 未加载
xori超过 7 年前
This was a weird blog post to read. I think I agree on all of your points (Promises should be lazy[-ish], cancellable and optionally synchronous) but disagree on all of your proposed solutions.<p>I do think `p = new Promise(fn);` shouldn&#x27;t kick off the `fn` immediately. But that it should start right away in the next event loop. I haven&#x27;t had issues with creating promise getters for repeatable calls. And think it organizes the business code away from the low level code.<p>I don&#x27;t see a problem with the original Promise.cancel() you proposed or how your lazy promises makes canceling them any easier.<p>And don&#x27;t we have `await` for the synchronous problem?<p><pre><code> console.log(await Promise.resolve(&#x27;hello&#x27;)); console.log(&#x27;world&#x27;) &#x2F;&#x2F; outputs &quot;hello&quot; &quot;world&quot;</code></pre>
评论 #16386125 未加载
BenoitEssiambre超过 7 年前
You don&#x27;t have to use promises: <a href="https:&#x2F;&#x2F;medium.com&#x2F;@b.essiambre&#x2F;continuation-passing-style-patterns-for-javascript-5528449d3070" rel="nofollow">https:&#x2F;&#x2F;medium.com&#x2F;@b.essiambre&#x2F;continuation-passing-style-p...</a>
kahnjw超过 7 年前
Agreed 100%. I did a bunch of js work about 3 years ago, used tons of promises. Then started a new job using Scala. The futures api in Scala is exactly what the author advocates, and it is definitely better for the reasons he gives.
acjohnson55超过 7 年前
He actually missed my least favorite thing about the promise API, which is that they fail silently. I&#x27;d argue that by default, an unhandled rejection should throw an exception at the end of an event loop, with an opt-in for the current behavior per-promise.
andrewaylett超过 7 年前
I seem to be in a minority, but rarely do I want to use the `new Promise()` mechanism for creating a promise, and I get the distinct impression that having it be the &#x27;default&#x27; is a bad idea -- the amount of times I&#x27;ve seen people wrapping up all their promise-related code inside the constructor, finishing off with `.then(function (x) {resolve(x)})` is disappointing :(.<p>async&#x2F;await solves much of this, of course, but where that&#x27;s not available I much prefer to keep all my async functionality actually async, and start off by using `Promise.resolve()`. Save the constructor for when you need to encapsulate some non-promise async code.
dsego超过 7 年前
Sindre built some great little modules that make working with promises a breeze. <a href="https:&#x2F;&#x2F;github.com&#x2F;sindresorhus&#x2F;promise-fun" rel="nofollow">https:&#x2F;&#x2F;github.com&#x2F;sindresorhus&#x2F;promise-fun</a>
kodablah超过 7 年前
This feels analogous to the problems w&#x2F; futures in Scala&#x2F;Java as they were first introduced. And the solutions are provided by libraries like <a href="https:&#x2F;&#x2F;monix.io&#x2F;" rel="nofollow">https:&#x2F;&#x2F;monix.io&#x2F;</a>.<p>So why are promises the problem instead of the lack of libraries on top of them? I understand cancellation cannot be fixed, but laziness sure can. As for synchronous execution, that&#x27;s just not gonna happen in event-driven-land. It doesn&#x27;t with other callback-based APIs (except AJAX which is deprecated) and I don&#x27;t see the complaints there.
fwip超过 7 年前
It sounds like the author wants coroutines, not promises.
评论 #16388211 未加载
rmrfrmrf超过 7 年前
You can have your referentially-transparent cake and eat it too, but the main problem is that no one has developed a decent library that marries FantasyLand-compliant wrappers with Promise interop.<p>There are about 8 million Task&#x2F;IO monad implementations and no one stopped for a second to think that `task.fork` could just return a Thenable and work with async&#x2F;await as expected.
评论 #16389108 未加载
phaedrus超过 7 年前
What do you think of C++17 coroutines? Of particular interest to you might be CppCon talks by Gor Nishanov, which are on Youtube.
fefb超过 7 年前
I rarely use promises. Just for the most simplest logics. When a package return promises in its API, I just use Rx.Observable.fromPromise(theRomise) . ReactiveX, especially RxJS, is so powerful to handle async events, from differents sources to differents logics. You can build powerful pipelines with it.
reaktivo超过 7 年前
A reminder that LazyPromises are trivial to implement:<p><pre><code> function LazyPromise(executor) { this.then = (resolve, reject) =&gt; new Promise(executor).then(resolve, reject); this.catch = (resolve, reject) =&gt; new Promise(executor).catch(reject); }</code></pre>
fiatjaf超过 7 年前
I don&#x27;t agree. Promises are a solution to the asynchronous callback world, not a dream spec someone came up with.<p>1. Eager, not lazy: Why is lazy better? Sometimes I want eager, I use promises. Sometimes I want lazy, I use a promise getter. Done. What if it was the other case, how would I turn a lazy promise into an eager one without messy code?<p>2. No cancellation. Events that permit cancellation are rare. Situations in which you would want to cancel something are rare. If you face these, use a promise library that does permit cancellation. Bluebird does it.<p>3. Never synchronous. If you want synchronous, just don&#x27;t use a Promise, use a function that takes another function. I don&#x27;t get the point about &quot;callbacks to sync&quot;. Callbacks are asynchronous. &quot;Synchronous callbacks&quot; may have this name, but they&#x27;re not actually callbacks, they&#x27;re functions. A function can take another function as a parameter, that doesn&#x27;t automatically make it into a &quot;callback&quot;.
avaq超过 7 年前
This article is a bit similar to one I wrote a while ago: <a href="https:&#x2F;&#x2F;medium.com&#x2F;@avaq&#x2F;broken-promises-2ae92780f33" rel="nofollow">https:&#x2F;&#x2F;medium.com&#x2F;@avaq&#x2F;broken-promises-2ae92780f33</a>
maximexx超过 7 年前
&gt; They force some behaviors to always happen even when it doesn’t make sense. That’s okay<p>No, it&#x27;s not. Promises suck, that&#x27;s all, no need to spend more words on it.
amelius超过 7 年前
The alternatives look nice. There&#x27;s just one requirement missing: streaming progress information to the listeners.
评论 #16386113 未加载
LordHumungous超过 7 年前
I thought &#x27;await&#x27; was supposed to solve the problem of going from async to sync?
0x7f800000超过 7 年前
Is it possible to modify Promises to be lazy through a Proxy?
singularity2001超过 7 年前
agreed with OP: being able to call async functions from &#x27;normal&#x27; code would be very kind of js.<p>res=fetch(&#x27;i-just-want-the-result.com&#x27;)<p>at least when scripting with node.js
codedokode超过 7 年前
I have a feeling the author doesn&#x27;t understand Promises well. In my opinion, they are in fact designed poorly, but I don&#x27;t see any problems with points the author describes.<p>He doesn&#x27;t like that the callback is called immediately - but Promises just represent a result that will be available later and do not guarantee (and should not) when the function will be called. If you want to delay some function call, do it explicitly or use a delay promise.<p>In my opinion, the main problem with promises is broken error handling. They don&#x27;t play well with exceptions. For example:<p><pre><code> var p = Promise(function (res, rej) { throw new RuntimeError(&quot;System is broken&quot;); }); </code></pre> This code will just ignore the error. While it is expected that the runtime error would float up and terminate the program - that is what runtime errors are made for.<p>This also makes writing tests more difficult because tests often use exceptions to indicate failure.<p>I have some ideas how to fix it (neither is perfect), but the comment will become too long.
评论 #16386514 未加载
评论 #16386710 未加载
评论 #16386905 未加载
评论 #16386550 未加载
评论 #16394095 未加载
评论 #16386557 未加载
IIIIIIIIIIIIIII超过 7 年前
This may get me in hot water here but...<p>I started working with JS promises specifically when they were barely available in a beta runtime. It took me over a year of working with them to really get a feel for them, now it&#x27;s been far longer. That&#x27;s because while you can &quot;understand&quot; the description and use it just fine, but a deeper comprehension and intuition takes much more time. I experimented a lot and insisted on writing my own helpers from scratch, without looking up other people&#x27;s code, because I wanted to get a <i>feeling</i> for the details.<p>This article seems quite artificial to me, the problems mostly made-up.<p>I don&#x27;t see the point of the first complaint. If you don&#x27;t want to start right away chain it to something that it should wait for. If it should not wait, then it can start right away. Her writes &quot;<i>Functions rescue us in this case because functions are lazy.</i>&quot; which I don&#x27;t quite understand: what is he running through promises if not functions? Hi &quot;betterFetch&quot; example mixes synchronous and promise syntax - how about using async&#x2F;await if you prefer the former? I admit though I don&#x27;t quite get the point of that example.<p>I don&#x27;t understand the whole &quot;run a promise&quot; idea either - because you don&#x27;t &quot;run a promise&quot;, that whole notion has nothing to do with what &quot;promise&quot; means. Just look at the word! It represents a (wrapped) future value. Where does the idea of &quot;running it&quot; come from? How do you &quot;run&quot; a (future) value?<p>You have a function and it is quite easy IMO: Using a promise you chain it to whatever you want to wait for. These days you can even use semi-synchronous syntax (async&#x2F;await). &quot;Running a promise&quot; makes no sense to me, you run functions, and I don&#x27;t see where the difficulty lies here?<p>The second point, cancellation, has been discussed very, <i>very</i> thoroughly - after all, this was on the table to be standardized. One of the issues he raises is the same as point one - if you have a chain it&#x27;s automatic. The main issue of cancellation is that you have zero control over the actual asynchronous operation that the promise actually stands for - because this is controlled by the OS alone! If you started I&#x2F;O, what does &quot;cancelling the promise&quot; mean?<p>1. If it is still waiting: If you don&#x27;t want to run something make sure the previous step returns a rejected promise. You can easily &quot;cancel the promise&quot;. Just let your promise function check something in the parent scope (via callback or it is in its lexical scope) when its chained function starts, and if that says &quot;you are canceled&quot; then don&#x27;t do it. You can put such a check as a standalone function anywhere in the promise chain you created, just let that &quot;amIcancelled()&quot; function throw or return a rejected promise. The whole chain aspect is something that the article is missing.<p>2. If the code is already running: you cannot cancel the actual (OS controlled) asynchronous operation, nor can you cancel a running JS function (unless you use async&#x2F;await see bottom paragraph).<p>I agree that promises are not perfect, but async&#x2F;await - not mentioned at all! - makes it a bit easier for many people - as long as they don&#x27;t forget one thing: Even if your functions now look like synchronous ones there is a fundamental difference: A synchronous JS function is never interrupted by any other code. An async function is suspended and other JS code gets to run in the middle of it when it encounters an &quot;await&quot;. This is something new first introduced by generators, before that JS functions were atomic (now some are not).
评论 #16386504 未加载
评论 #16386728 未加载
评论 #16386288 未加载
评论 #16386493 未加载
tobltobs超过 7 年前
Promises are classic JS clusterfuck. Replace something shitty, like nested callbacks, with something even more shitty. Thereby ignoring 40 years of experiences of other languages.
评论 #16386965 未加载
评论 #16386969 未加载