Hi all, I just remembered about this thread after recent email by raster. What jpeg points out is that it's strange, because it IS strange and thus not right -- unlike Cedric said.
When you fulfill a promise (ie: feed a value to Future, delivered via the callback specified in efl_future_then()), you send a value. This value can be: - an actual value, called 'resolved' - another promise. This can be implemented with Eina_Value, by defining a new Eina_Value_Type for future. Thus the function signature is plain simple as in JS: eina_promise_fulfill(p, eina_value); That said, take the other, the reader/future side: f = some_function_returning_a_future_for_a_promise(); f2 = efl_future_then(f, cb1); f3 = efl_future_then(f, cb2); Internally, these future are a linked list: f->next = f2; f2->next = f3; This is built inside efl_future_then(). In the above example "f" is the only future with an associated promise. Others are 'shallow': f->promise = <some_function_returning_a_future_for_a_promise_PROMISE> f2->promise = NULL f3->promise = NULL That is cross-linked with the promise: <some_function_returning_a_future_for_a_promise_PROMISE>->future = f Back to the WRITER (promise) side, when you fulfill: efl_promise_fulfill(promise, value) { future = promise->future; promise->future = NULL; // avoid access to future of this promise again // let people NULL their pointers report_weak_refs(future); report_weak_refs(promise); next_value = future->fulfill(p->future, value); if (!next_value) { // report error, similar to 'fulfill' of an actual value procedure below: // find first 'future->error', call it from a job. } if (eina_value_type_get(next_value) == EINA_VALUE_TYPE_FUTURE) { eina_value_get(next_value, &value_future); // value_future is a recently created future and will // NEVER have 'fulfill' or 'error' set, if they do, it's a bug (ERR...) // MAYBE they have weak refs due bindings (unref them). // --- // OTOH, future->next WILL likely have fulfill/error/weak-refs // since it was created by efl_future_then() // thus we steal the promise and set it in it if (future->next) { next_future = future->next; // steal next_future->promise = value_future->promise; next_future->promise->future = next_future; if (value_future->fulfill || value_future->error) { ERR("the future value must NOT have an associated error/fulfill! future=%p", value_future); // maybe call error in value_future and chain value_future->next ...? } value_future->promise = NULL; report_weak_refs(value_future); eina_value_free(next_value); // free it return; // need to wait for fulfill or reject/error } else { return; // if it was the last 'fulfill'/'error', do not free // just let it do its work and then fullfill/reject to nothing } } else { // actual value, 'resolved' // find first future with an "fulfill", values are propagated. while ((future) && (!future->fulfill)) { // this future has no promise, so we just call it done: report_weak_refs(future); next_future = future->next; // do it after weak refs, eventually 'next' is gone via 'cancel' free(future); future = next_future; } // no future to receive the data, we're done if (!future) { eina_value_free(next_value); // not used, bye bye return; } ctx = malloc(sizeof(*ctx)); ctx->future = future; ctx->value = value; job_add(future_fulfill_job, future_fulfill_canceled, ctx); // do it from next main loop iteration, "clean context" } } } future_fulfill_job(data) { ctx = data; report_weak_refs(future); next_value = ctx->future->fulfill(ctx->future, ctx->value); // --- // HERE GOES THE SAME (or similar) code to the one in efl_promise_fulfill()!!! // ensures the chaining // --- // no 'eina_value_free()', value is owned by 'then'! free(ctx->future); free(ctx); } future_fulfill_canceled(data) { // main loop del... eina_value_free(ctx->value); report_weak_refs(ctx->future); // maybe ctx->future->error() ECANCELED? free(ctx->future); free(ctx); } With you example, it would actually be: main_func(url) { // this specifies ALL the pipeline: Efl_Future *f1 = download_url(url); Efl_Future *f2 = efl_future_then(f1, _down_cb); // would be better called 'do_unzip' Efl_Future *f3 = efl_future_then(f2, _unzip_cb, image); // would be better called 'do_load' efl_future_then(f3, _load_cb, image); // do_show } Eina_Value *_down_cb(void *data EINA_UNUSED, Eina_Value *value) { const char *filename; Efl_Future *next_future; Eina_Value *next_value; // be safe and get actual value... we could offer a nicer 1-line macro for these 2 EINA_SAFETY_ON_TRUE_RETURN_VAL(eina_value_type_get(value) != EINA_VALUE_TYPE_STRING, NULL); eina_value_get(value, &filename); // likely done in a single line in real code next_future = _async_unzip(filename); // do NOT add anything to 'next_future!' next_value = eina_value_future_new(next_future); eina_value_free(value); // we're done with it, but you could keep it for later usage... return next_value; // which is actually a promise, "chaining" } Eina_Value *_unzip_cb(void *data, Eina_Value *value) { Image *image = data; const char *filename; Efl_Future *next_future; Eina_Value *next_value; EINA_SAFETY_ON_TRUE_RETURN_VAL(eina_value_type_get(value) != EINA_VALUE_TYPE_STRING, NULL); eina_value_get(value, &filename); next_future = _async_load(image, filename); // same as _down_cb: next_value = eina_value_future_new(next_future); eina_value_free(value); return next_value; } Eina_Value *_load_cb(void *data, Eina_Value *value) { Image *image = data; show(image); return value; // we could 'eina_value_free()' and return a dummy, 'empty' value... // or we just propagate it, it will be freed as we're the last 'then' } I hope it clarifies and guides next implementation :-) On Fri, Sep 30, 2016 at 3:17 PM, Cedric BAIL <cedric.b...@free.fr> wrote: > Hi, > > Le 30 sept. 2016 07:51, "Jean-Philippe André" <j...@videolan.org> a écrit : >> Here's a question about promise chaining. C being what it is, it's harder >> to define a series like p.then().then() than it is in JS. And I can't find >> any good example in EFL code. >> >> Let's consider an abstract chain that I'll define as "download, unzip, >> show". (eg. download a zip file, unzip it and set the file on an image >> object). >> >> >> All of these are async, we don't care about the result of show. I don't >> understand how the chain should be written: >> >> main_func(url) { >> Efl_Future *f1 = download_url(url); >> Efl_Future *f2 = efl_future_then(f1, _down_cb, ..., NULL); >> efl_future_then(f2, _unzip_cb, ..., image); >> } >> >> // So far I think we're ok >> >> void _down_cb(null, event) { >> Efl_Promise *next = event->info->next; >> Efl_Future *f3 = _async_unzip(event->infodata); >> efl_future_then(f3, _unzip_cb2, .., next); >> } >> >> // I'm already getting confused with next, f2 and f3. Something is odd. > > Yes, that's why there is some function (I don't have the code at end), to > feed a future into a promise. This connection take care of propagating the > value to the next promise. It avoid having to setup your own callback to do > so as you did in this example. > >> void _unzip_cb(data, event) { >> Image *image = data; >> show(image); >> } >> >> void _unzip_cb2(data, event) { >> Efl_Promise *p2 = data; >> efl_promise_value_set(p3, NULL, NULL); // --> calls unzip_cb >> } >> >> >> So my questions: >> 1. Is this above code correct? > > Yes. > >> 2. (How) are we ensuring that the chain eventually resolves? > > You mean, that if you don't set anything on the next promise, nothing will > happen ? There is nothing there we can do I think, as everything being > asynchronous, we can't know if there is a good reason that the value hasn't > been set after we leave the callback. > > Possible hack for this is to unref the next promise. If the callback hasn't > refed it nor set a failure/value, it will trigger an error and trigger a > failure on the next future. Doable, but not sure if the value.do you think > it's worth it ? > >> 3. Couldn't this be made in a simpler way? unzip_cb2 is really just an >> automatic resolve of another promise/future. > > Yes, as said before, chaining imply we should be able to set a future as a > value on a promise. > >> In unzip_cb, if I ignore "next", f2 will never come to completion. >> >> So here's a proposal: >> >> 1. Get rid of info->next. Instead: >> >> Efl_Promise *p2 = efl_promise_next(event->object); >> >> That way, EFL knows that the developer has not ignored the promise. The >> developer must then call efl_promise_value/error_set at some point, sync > or >> async. > > This can not work.every set of callback need a different next promise > otherwise only one of them can really use it. For the same reason, multiple > st if callback are useful, we need to associate multiple next promise. > >> If the callback doesn't call efl_promise_next, EFL knows that the "next" >> promise will never come to completion, ever. In that case, EFL >> automatically calls efl_promise_value_set(next, NULL, NULL) to keep > walking >> the chain. (Another idea is to instead cancel it). > > See above proposition to do the same and still have multiple callback. > >> According to [ https://promisesaplus.com/ ], if onResolve is not a > function >> (success_cb is NULL), then p2 is fullfilled with the same value as p1. So > I >> guess we would just call _unzip_cb directly in the above example. > > Yes, that's something I plan to handle, setting null function well just > propagate the value/error/process down the chain. > >> Without efl_promise_next() the callback must always handle "next". Failure >> to do so will break all chains, and leak. The value of "next" can be set >> inside _efl_loop_future_success. > > Yes, but it's kind of a false problem anyway. If you set expect a future > and never get it triggered and you're the one who is in charge of the > promise, you have maximum 2 places where you may have forgotten to set a > value/trigger a failure. We can improve the detection as proposed above, > but I don't think it's a real problem. > >> 2. (raster's idea) Get rid of event->info >> >> Similarly, use efl_promise_value_get() instead of event->info->value. That >> way we can implement stealing with efl_promise_value_steal(&val, > &freefunc) >> and keep the symmetry with value_get(). > > I am not a fan of area stealing value. It may create slight problem with > multiple callback and other user have to be aware of what you're doing > (order if registering callback also matters in that case). So I would think > that if you want the ability to steal a value, we should actually have a > different efl_future_steal that allow only one callback and prevent any > other efl_future_then to be registered (and trigger an error if any are). > Which means, that value get is pretty much unnecessary. We can have it, as > it solve the issue we have by reusing efl_event_cb (but maybe we should try > to solve that instead of going around it). > >> In that case, efl_ref(promise) can be used to keep a value alive for as >> long as you want. It's just a storage object. [if not eo, then > promise_ref, >> whatever] >> Any weak ref by efl_promise_use probably needs to be reset to NULL after >> the success/fail callback. > > That is array perfectly doable and the expected behavior today with > efl_promise/efl_future (the c++ binding use that on future). > >> 3. multiple then, race, all order of operations >> WIth steal, and any kind of cleanup callback, the order of operations is >> crucial. Is it well defined? Example: >> >> f1 = function(); >> efl_future_then(f1, _then1, ...); >> efl_future_then(f1, _then2, ...); >> efl_future_then(f1, _then3, ...); >> // then1, then2, then3 MUST be called in this order (I think) >> >> f2 = function(); >> efl_future_then(efl_future_all(f1, f2), _then4); >> efl_future_then(f2, _then5, ...); >> // will it be _then4, _then5 or _then5, _then4? >> // same question for race >> // I believe the implementation is fine here, but not many docs/tests/uses >> :( > > Yup, you can see the problem. > >> Thoughts? >> Is the above OK or completely off track? > > I think there are easier way to solve this problem without breaking > everything ! > >> PS: Sorry this mail became longer than I thought at first :-/ > > Raster bad influence I guess ! :-) > > Cedric > >> -- >> Jean-Philippe André >> > ------------------------------------------------------------------------------ >> _______________________________________________ >> enlightenment-devel mailing list >> enlightenment-devel@lists.sourceforge.net >> https://lists.sourceforge.net/lists/listinfo/enlightenment-devel > ------------------------------------------------------------------------------ > Check out the vibrant tech community on one of the world's most > engaging tech sites, SlashDot.org! http://sdm.link/slashdot > _______________________________________________ > enlightenment-devel mailing list > enlightenment-devel@lists.sourceforge.net > https://lists.sourceforge.net/lists/listinfo/enlightenment-devel -- Gustavo Sverzut Barbieri -------------------------------------- Mobile: +55 (16) 99354-9890 ------------------------------------------------------------------------------ The Command Line: Reinvented for Modern Developers Did the resurgence of CLI tooling catch you by surprise? Reconnect with the command line and become more productive. Learn the new .NET and ASP.NET CLI. Get your free copy! http://sdm.link/telerik _______________________________________________ enlightenment-devel mailing list enlightenment-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/enlightenment-devel