Closed dfahlander closed 9 years ago
External Promise implementations can have features that would be lost if converting them to the 'standard' Promise as suggested in the readme of this repository.
For reference, where in the README is this mentioned?
Those sections are only informative and not totally representative of an implementation.
Ok :+1:
Do you know if it has been discussed require this kind of adaptivity for async?
@dfahlander compositional functions? Although not exactly.
Ok, that's an even broader approach. Is it still an open issue on how to accomplish this? Can it be done without having to configure the behavior of async/await? Or would it be acceptable to have a least common denominator, such as an interface that all asyncronic primitives would have to implement to work with async/await?
Ok, sorry. I saw that compositional functions use different keywords for async. That's fine. What I am interested in is a fine tuning of the async/await spawning method.
This is an interesting idea but I'm not sure how well it would work in practice. I think this is morally equivalent to passing through promises returned from then handlers, and has the same issue. Namely, if you think about the following code:
async function get(url) {
if(cache[url]) return cache[url]; // no promise returned, so wrapped in built-in promise
let data = await $.get(url);
if(!url.endsWith('.json')) return data; // jQuery promise returned
return await handleJSON(data); // whatever kind of promise handleJSON returns
}
get('foo.json').? // what can I call on this promise?
Depending on how you call get, you get a different promise. Today libraries use many different promise libraries so this problem will be quite pervasive. Worse, it seems like it will be very difficult to debug/work around when you need to. Thoughts?
Yeah it seems better if you want a specific kind of promise to do something like
function bb(fn) {
return (...args) => BluebirdPromise.resolve(fn(...args));
}
const get = bb(async url => {
if (cache) ...
});
otherwise you just get the standard Promise.
@bterlson Yes, I've been thinking about that and it's a problem unless the function author understands the rules of how async would work. If the author of the async function want to ensure a single Promise type to return, (s)he could start with an await expression that returns the desired promise type to unify the rest of the function.
async function get(url) {
await CustomPromise.resolve(); // This line will unify all promises below to CustomPromise
if(cache[url]) return cache[url];
let data = await $.get(url);
if(!url.endsWith('.json')) return data;
return await handleJSON(data);
}
Note that this is only required when you are mixing promise implementations or returning something directly in the same function. In most cases people will likely stick to a single Promise implementation in a same function.
@domenic Your suggestion might work for some use cases. But at least in my use case, the internal steps between the await calls must be done using Dexie.Promise. Otherwise I would loose the indexedDB transaction between the await calls due to an incompatibility between indexedDB and most ES6 Promise implementations. I would also loose the Promise-specific data feature between the calls.
I don't think you should be using async/await if you need implementation-specific features. It's not a general method for triggering .then
calls; it's a way of doing standard promises (which can be hopefully optimized even better given the guarantees standard promises make).
It is a better design to be dependent on an interface rather than on an implementation. Especially for a built-in keyword.
It's not unlikely that more innovative Promise features will be developed in libraries. Like Dexie has its PSD, other libs has cancellation tokens and God knows what will show up out there...
Also, we have the Task and Observable primitives trying to be addressed by Compositional functions.
What about this: Instead of expecting a Thenable (as I suggested), require an Awaitable interface more simplistic and maybe more optimizable than Thenable. It's use case is only to be called from the async engine.
interface Awaitable {
await(onSuccess: Function, onReject: Function): Awaitable;
}
Awaitable.await would be simpler than Promise.then: It only needs to supports a single listener onto each instance.
Awaitable.await would be compatible with Promise.then. To support it in standard Promise, do Promise.prototype.await = Promise.prototype.then.
I think that since async/await is such a great improvement of the language, all libraries would directly support the Awaitable interface including Task and Observable.
It wouldn't be an issue if all libraries out there, including non-promise asyncronic primitives. would have to add 'await' function to their prototype in order to work with async/await. This would not only solve the issue I have in Dexie.Promise but also Compositional functions without introducing new keywords like 'task' and 'observable'.
This proposal is not a general method for triggering .await calls; it's a way of doing standard promises (which can be hopefully optimized even better given the guarantees standard promises make).
I still believe it is better to be dependent on an interface rather than on an implementation. Especially for a built-in keyword.
I don't think we will pursue this for now. Support for promise subclasses is a good argument for composition functions. For async functions it makes more sense to use the same behavior as promises WRT subsuming other promise types. If you want a "FooPromise" out of an async function you could also convert with var p = FooPromise.resolve(asyncFunction()). It would also probably be easy with decorated async functions.
@bterlson - the issue with FooPromise.resolve(asyncFunction()) is that features will get lost between the awaits in the async function.
As a result, these promise implementations will not be able to use async/await.
It's a good point that when using async functions to await non-native promise-returning functions will result in the non-native promises being subsumed by native promises. You suggest a special case for libraries that have the same signature for .then, which might help (at some cost) but doesn't address libraries that have special conventions for, for example, propagating the cancellation signal, on progress handlers, etc.
In general I believe that developers should know exactly the kind of promise they're going to get from an async function (whether its because async functions always return native promises or because the async function is tagged in some way to indicate its return type ala composition functions). I don't think it's acceptable to let callees of an async function decide what type of promise the function returns. This seems like asking for subtle bugs and breakage when libraries change.
@dfahlander ... i agree with you... we use $q with some monkey patching that allows us to capture "unhandled rejections", that is, promises that are rejected but don't have a rejected handler which we consider to be unhandled exception... anyway... i for this and other reasons would like to use our promises from async functions... i am having a hard time understanding the reasoning behind having to use the global Promise... anyway... thanks for your attempt..
It would be a good thing if async functions would return the same type of promise that was awaited on in their function bodies. This would align with how typescript is implementing it (according to this issue). The async/await keywords could then be used together with various promise implementations without loosing the bells and whistles that comes with various Promise libraries such as bluebird, Q, WinJS and Dexie.
External Promise implementations can have features that would be lost if converting them to the 'standard' Promise as suggested in the readme of this repository.
Therefore, I would propose a more adaptive implemetation of spawn() that can accept and forward any A+ compatible Promise that was awaited for:
Specifically, for my library, Dexie.js, there is a feature that enables 'Promise-Specific Data' which is analogue to 'thread static data' for promises. The entire Dexie API depends on that feature because it enables things like reentrant mutexes, implicit transaction scope, sub transaction awareness and some more API sugar. I believe that bluebird, Q, WinJS and others also would benefit from having this openess in how async/await behave.
By not binding the async keyword to a specific implementation of Promise, we would allow new Promise implementations with features that we cannot think of today and still use them with async/await.