Reflection to know if executed within a generator/async ?
I don't think being executed in a generator or async function is a good signal that a promise should be returned (something needs to "await" the promise). The async function is really a chain of synchronous code split at various points, during the synchronous parts the code would still need to be synchronous and returning a promise would be wrong. The real data you need is "will this value be consumed by an 'await' expression or be used to resolve a Promise and, in the general case, that is not something that can't be answered at the time your example function is executing.
Uhm... you never closed that "will this value be .... thing and I'm expecting a "dary" answer like in few emails but my whole point is consuming an API in different scenarios: held or synchronous.
You can always create an API that always returns a Promise, that's nothing new for the language, but can you decide if it's he case to absolutely return a promise 'cause any other value would cause trouble within that generator or async function?
I am against any semantics that expose how a function is used. I struggle and many others do when this occurs in CSS (display varies depending on container). I would also be hesitant to encourage things acting both synchronously and asynchronously depending on how they are used. Changing temporal behavior depending on usage is asking for race conditions.
How would you detect that the following call to your fileGetContent
function should return a Promise?
function oldSchool() {
return fileGetContent("foo").then(function (c) {
// ....
})
}
I agree with Bradley, and kindly refer you to this oft referenced blog post regarding synchronous and asynchronous APIs: blog.ometer.com/2011/07/24/callbacks-synchronous-and-asynchronous
Ron
From: Bradley Meck Sent: Thursday, December 3, 2015 8:57 AM To: Andrea Giammarchi Cc: es-discuss at mozilla.org Subject: Re: Reflection to know if executed within a generator/async ?
I am against any semantics that expose how a function is used. I struggle and many others do when this occurs in CSS (display varies depending on container). I would also be hesitant to encourage things acting both synchronously and asynchronously depending on how they are used. Changing temporal behavior depending on usage is asking for race conditions.
On Thu, Dec 3, 2015 at 10:37 AM, Andrea Giammarchi <andrea.giammarchi at gmail.com<mailto:andrea.giammarchi at gmail.com>> wrote:
Uhm... you never closed that "will this value be .... thing and I'm expecting a "dary" answer like in few emails but my whole point is consuming an API in different scenarios: held or synchronous.
You can always create an API that always returns a Promise, that's nothing new for the language, but can you decide if it's he case to absolutely return a promise 'cause any other value would cause trouble within that generator or async function?
On Thu, Dec 3, 2015 at 4:15 PM, John Lenz <concavelenz at gmail.com<mailto:concavelenz at gmail.com>> wrote:
I don't think being executed in a generator or async function is a good signal that a promise should be returned (something needs to "await" the promise). The async function is really a chain of synchronous code split at various points, during the synchronous parts the code would still need to be synchronous and returning a promise would be wrong. The real data you need is "will this value be consumed by an 'await' expression or be used to resolve a Promise and, in the general case, that is not something that can't be answered at the time your example function is executing.
On Thu, Dec 3, 2015 at 4:15 AM, Andrea Giammarchi <andrea.giammarchi at gmail.com<mailto:andrea.giammarchi at gmail.com>> wrote:
Hi there, just writing down some thoughts about being able to understand if a method/function has been executed within a generator/async and is being yielded/awaited.
Rationale: API that would like to behave synchronously in some case, returning Promises in other cases.
Example:
function fileGetContent(fileName) {
// random example
if (held) {
return fetch(fileName).then((r)=>r.text());
} else {
var xhr = new XMLHttpRequest;
xhr.open('GET', fileName, false);
xhr.send(null);
return xhr.responseText;
}
}
Above example will virtually return always the same type and it could work inside a generator or an async function as long as it's being held.
Does any of this make sense? Is it worth exploring this pattern?
Thanks for any sort of thought.
Best
I guess held
would be like an arrow function, "transparent" when it comes
to held invokes (like context or arguments)
Ron it's not about being ambiguous, it's actually the opposite: know when returning directly a value would actually break "outside expectations"
So my idea is to indeed offer either a sync or an async result and not both (in terms of same execution context/scope/logic)
Le 3 déc. 2015 à 20:04, Andrea Giammarchi <andrea.giammarchi at gmail.com> a écrit :
I guess
held
would be like an arrow function, "transparent" when it comes to held invokes (like context or arguments)
? Sorry, but I don't understand how that would help to answer my question.
Sorry I misread your code. Your case assumes fileGetContent always returns a Promise so my proposal won't be useful there because it's already used as Promise.
My idea is more about migrating to full async code without changing all the things around, giving an API the ability to behave differently.
Maybe it's too complicated or too magic to implement, that's OK anyway.
I think that migration path is typically 1) make the breaking change so that everything returns a promise ASAP, 2) seamlessly migrate sync parts to async at your leisure, without a breaking change.
Le 3 déc. 2015 à 22:07, Andrea Giammarchi <andrea.giammarchi at gmail.com> a écrit :
Sorry I misread your code. Your case assumes fileGetContent always returns a Promise so my proposal won't be useful there because it's already used as Promise.
You can remove that assumption by replacing fileGetContent("foo")
with Promise.resolve(fileGetContent("foo"))
.
My idea is more about migrating to full async code without changing all the things around, giving an API the ability to behave differently.
The key fact of my example is that I can (and do) write full async code without generators or async functions (just with ES3 + Promise), and you have no way to detect that. For example, the following code:
function bar() {
return Promise.resolve(fileGetContent("foo")).then(function (c) {
// whatever
})
}
is functionally equivalent to:
async function bar() {
const c = await fileGetContent("foo")
// whatever
}
In both cases, I can receive a value or a promise for a value, and in both cases getting a promise is strictly better.
I don't want to be served an inferior version of fileGetContent
in the first case just because you were unable to guess my intentions,
and I won’t hurry to migrate my existing code to use the second pattern, because the difference is only cosmetic.
Jordan please let's avoid discussions about migration patterns, I'm sure we've all done that and we all know how to. This is not the point of my idea.
Claude, again, you are using promises already so you won't need this "magic". Your API returns promises, you are good to update to async/await or generators.
However, in the future you won't write promises directly if not as returning values within some method .... right ?
Looking at your code, I would always go for the second pattern instead of the first one. So what if you'd like to use that already, simply adding async and await and the only change to do in a well known/used synchronous API would be to check if the returned value is held and behave accordingly?
That would give you a way to easily migrate to non-blocking as API consumer, and an easy way as API author to update to a non blocking version without breaking compatibility with other consumers.
In this scenario, you are still free to use Promise.resolve( fileGetContent("foo"))
which not on hold and not concretely asynchronous
in terms of fileGetContent("foo")
operations, but as soon as you go for
async/await or a generator that function will return a Promise for you.
I hope it's clear now what is my idea.
The more I read about this the more I think that await
is what you want
to change not the behavior of functions. Rephrased to what I think is
being asked:
If await
encounters a non-thenable value, Promise.resolve
the value
before awaiting.
Isn't this already the case?
Eyes don't see, mind don't care?
The fact await doesn't break, if that's the case, is cool and glorious. The fact a consumed API would like to keep working with synchronous projects and realy behave asynchronously with most modern one, is a different thing.
To better describe a common use case, think about CommonJS
require('module')
API.
There's no way it should break on all platforms (not jsut node and
browsers) that implemented CommonJS, it could still return a Promise,
through my held
like idea, for Web and new async based code.
async works with old code, old code cannot be promoted to async one. All I am trying to explain is that I'd love to give well known, de-facto standard, universally used APIs the ability to behave, for real, asynchronously whenever the future of the language is available.
How bad is that?
Be very clear, this is interesting, but dangerous.
I care very much but not in a positive way due to ironing out what would happen if the WHATWG Loader spec were to use Promises (either under the hood or explicitly). The amount of possible timing differences caused whatwg/loader#54 which caused a huge refactor of the entire spec since timing implications of Promises are very different from synchronous code.
My thoughts on things can be summed up as: if timing change implicity/based upon situations outside of your code -> side effects change -> code can
break based upon when it is being used (rather than how it is being called).
Didn't know about that thread, interesting indeed.
My point in a TL;DR version: if the future is able to deal with sync code satisfying async calls, why couldn't the future be even better giving old sync code the backward-compatible ability to understand if it's invoked in a future-like defined constrain such a generator or an async callback ... it looks like a limit from the future of the language, rather than a real limit of what the current ES5/6 specification can provide.
Hence my held
idea.
Le 4 déc. 2015 à 18:31, Andrea Giammarchi <andrea.giammarchi at gmail.com> a écrit :
async works with old code, old code cannot be promoted to async one. All I am trying to explain is that I'd love to give well known, de-facto standard, universally used APIs the ability to behave, for real, asynchronously whenever the future of the language is available.
How bad is that?
I think that the only place where you can safely provide a promise when a value was previously expected, is when you are in tail-position inside an async function (as part of either await
or return
). The "tail-position" restriction is bad for your case, because it is easy to switch between tail- and non-tail-position by refactoring, and thus switching semantics unexpectedly.
Given that sync and async code may behave differently (race conditions...), picking randomly (because of unreliable guess) one semantics or the other is going to make it more difficult to debug.
To Andrea:
A function like the one you describe would be practical only if, when called in a sync context, it could wait by blocking, unlike when being called in an async context. Otherwise, it doesn't work:
let theValue;
function fetchTheValue() {
return fetch('/theValue')
.then(r => r.text())
.then(v => theValue = v);
}
function getTheValue() {
if (theValue == null) {
if (held) { return fetchTheValue(); }
!!! return what?
}
if (held) {
return Promise.resolve(theValue);
}
return theValue;
}
This example shows that if you don't have the value, but call the function from an async context, you will get the value. But if you call it from a sync context you don't get the value. It means that without waiting-by-blocking, your function behaves differently depending on the context in which it's called.
Waiting-by-blocking is usually a bad thing and I believe it shouldn't be in the language (it can be provided by the host environment).
Also I don't understand how the yielding context would be the same as the await context, since you can yield synchronously. If I write:
yield getTheValue();
why should held be true?
Actually I may have misunderstood. I guess you want held to be false in the yielding context.
Andrea Giammarchi schrieb:
simply adding async and await and the only change to do in a well known/used synchronous API would be to check if the returned value is held and behave accordingly?
No, making a consumer of an API asynchronous should not be simple. Unless you are only writing pure functions, everything that involves states will very likely break. Concurrency is not trivial, it needs a lot of consideration.
as soon as you go for async/await or a generator that function will return a Promise for you.
Please never do that. Functions that sometimes return promises and sometimes not are already known to be an antipattern. Making this obscurely dependent on some fragile syntactic conditions could only make this worse.
If you want to migrate your library to asynchrony, just make it a breaking change. Your consumers will thank you for it.
Kind , Bergi
I've asked for opinions and if in 2 days I haven't replied means I got it my idea is not welcome which is OK and fair enough.
However, I'm curious to know about this "Functions that sometimes return promises and sometimes not are already known to be an antipattern" because I have a library that does that in somehow explicit way (if you pass a callback it doesn't return a promise, it invokes such callback once resolved) and it works without any real-world problem.
Mind pointing me at the library that failed returning Promises arbitrarily?
Le 7 déc. 2015 à 07:48, Andrea Giammarchi <andrea.giammarchi at gmail.com> a écrit :
I've asked for opinions and if in 2 days I haven't replied means I got it my idea is not welcome which is OK and fair enough.
However, I'm curious to know about this "Functions that sometimes return promises and sometimes not are already known to be an antipattern" because I have a library that does that in somehow explicit way (if you pass a callback it doesn't return a promise, it invokes such callback once resolved) and it works without any real-world problem.
Mind pointing me at the library that failed returning Promises arbitrarily?
The blog post pointed by Ron earlier in this thread contains a discussion about how sync and async code differ, and thus why it is generally not a good idea to execute random code sometimes asynchronously and sometimes not (with a pointer to a concrete example). It is worth reading.
I have an objection to that, async is just a special case of sync, like square to rectangle. Why shouldn't the promise be inviolable?
Andrea, it's one thing to either return a Promise or use a callback, depending on the existence of the callback, but I get the impression the discussion is about difference between fs.readFile and fs.readFileSync, and why those should be separate functions.
I've never said they shouldn't, the only example I gave was require
which
is a very specific use case. We can talk about everything else as much as
we like but it would be irrelevant for this post or regarding what I was
thinking about ;-)
On Sun, Dec 6, 2015 at 10:48 PM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:
However, I'm curious to know about this "Functions that sometimes return promises and sometimes not are already known to be an antipattern" because I have a library that does that in somehow explicit way (if you pass a callback it doesn't return a promise, it invokes such callback once resolved) and it works without any real-world problem.
This pattern is fine; different overloads of a function are basically different functions, and we're using this pattern in the web platform in a few places to "update" some CB-using APIs to Promises. As long as the function previously returned void and took the CB last, we can do it.
yup, that's what my library does as well. I thought Bergi meant in absolute.
On Tue, Dec 8, 2015 at 9:44 AM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:
yup, that's what my library does as well. I thought Bergi meant in absolute.
I can't speak for Bergi, but yeah, I assume he was talking about a function returning a promise or not based on something other than overloads, such as the value of certain arguments, or the syntactic location in a greater expression.
just writing down some thoughts about being able to understand if a method/function has been executed within a generator/async and is being yielded/awaited.
Rationale: API that would like to behave synchronously in some case, returning Promises in other cases.
Example:
function fileGetContent(fileName) { // random example if (held) { return fetch(fileName).then((r)=>r.text()); } else { var xhr = new XMLHttpRequest; xhr.open('GET', fileName, false); xhr.send(null); return xhr.responseText; } }
Above example will virtually return always the same type and it could work inside a generator or an async function as long as it's being held.
Does any of this make sense? Is it worth exploring this pattern?
Thanks for any sort of thought.
Best