- From: Benjamin Young <bigbluehat@hypothes.is>
- Date: Wed, 7 Oct 2015 15:43:35 -0400
- To: Ivan Herman <ivan@w3.org>
- Cc: Bill Hunt <bill@opengovfoundation.org>, Doug Schepers <schepers@w3.org>, W3C Public Annotation List <public-annotation@w3.org>
- Message-ID: <CAE3H5F+ft5G50ahLqWVQwsq8gCjC_SGEF9=Hh5XvbkYzCO3akg@mail.gmail.com>
Yeah...I don't think I'd go so far as to use them together...though one certainly could. I'm afraid the article you linked to tangled the wires for me a bit... Here's the article that's helped me the most wrt Promises (fwiw): http://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html Maybe that helps a bit. :) On Wed, Oct 7, 2015 at 7:52 AM, Ivan Herman <ivan@w3.org> wrote: > Well… I tried to understand how generators and promises work together. It > is a bit like a lame leading the blind, in the sense that I do not have a > really really comfortable feeling about Promises in complex situations; as > for generators, I am familiar with them in Python, but the ES6 version is > more complex. I have gone through some of the examples and texts around; my > pattern comes from [1]. Based on the patterns I found in [1], here is a > structure that *may* work with the current interface: > > function runSearch( params, generator ) { > var iterator = generator(param), ret; > (function iterate(val){ > // This is where the control goes back to the "runSearch" part > // and the match result is sent back > ret = iterator.next(val); > if( !ret.done ) { > // ret.value is the Promise set by FindText.search() > // the 'then' part is when the next match is found > // the iteration will get the result back to the 'search' part below > ret.value.then( function(match) { > iterate(match.result) > }); > } > })(); > } > > //==== > > params = { ..findtext params...}; > runSearch( params, function *search() { > var range; > do { > // Note that the result of the yield is what the corresponding 'next' > sends > // ie, it will be the match result. > // This is also where the async part kicks in, because the .search(), > returning a Promise, leads to it. > match = yield find_text.search(); > if( match.result ) { > // do something with match.result > } > } while( match.result ); > }) > > > I have no idea whether this makes sense, ie, whether that works. But maybe > more importantly: I still think it is very complicated, requires a thorough > understanding of complex things, ie, I am not sure that it would be the > right level of abstraction for the API. And it works with ES6, although I > agree that it may be acceptable for the API to rely on the ES6. > > Just an idea: what about hiding all this to the end user? Isn't it > possible to say that the result of search() is actually an iterator in the > ES6 sense, and it is up to the API implementation to hide all the async > complexity? Or should we keep to one single searchAll() that would return > an iterator and stop there? > > Ivan > > [1] http://davidwalsh.name/async-generators > > > > On 06 Oct 2015, at 17:17 , Benjamin Young <bigbluehat@hypothes.is> wrote: > > Could ES6 generators be employed here? > > http://www.ecma-international.org/ecma-262/6.0/#sec-generator-function-definitions > > It currently has to be polyfiled, but perhaps the future is not far off. ;) > http://kangax.github.io/compat-table/es6/#generators > > That could get you something like: > ``` > var rf = new FindText({ text: "Rage, rage" }); > var result = rf.search() > var next_result = rf.next(); > ``` > > Which seems to be what one would expect (vs. a promise-based thing). > > `searchAll()` could return a Promise for the purpose of asynchronous code > and avoiding callbacks. > > Great start, though, Doug, regardless! > > > > On Tue, Oct 6, 2015 at 11:04 AM, Ivan Herman <ivan@w3.org> wrote: > >> Ah yes! This recursive construction to stack up promises is the solution >> I indeed saw (it may have been in one of the blogs of Jake Archibald) and I >> already forgot; and it always makes me understand it again and again:-) >> >> You made me realize that using search() like that gives a fake impression >> of performance gain without being one, right? As you say, in fact all the >> promises can return with success when the last search() has also been >> executed; ie, performance wise, we do not really gain anything compared to >> a searchAll(). Would it mean that we should not use search() at all? >> >> Thanks >> >> Ivan >> >> >> >> On 06 Oct 2015, at 16:54 , Bill Hunt <bill@opengovfoundation.org> wrote: >> >> Hi Ivan, >> >> Those are actually the precise concerns I brought up to Doug yesterday, >> and agree that searchAll() is a fine solution. I also proposed that the >> function could take a "limit" parameter, to only get N results instead of >> all. This makes promises much easier. Here's the body of my original >> message that illustrates the point in detail. >> >> Cheers, >> -Bill >> >> >> >> >> Here's Example 1, as-is, with promises, to get all until it can't find >> any more results: >> >> var results = []; >> var recurseSearch = function(rf, results) { >> var allDonePromise = new Promise(); >> >> var searchPromise = rf.search(); >> searchPromise.then( >> function(matchData) { >> if(matchData) { >> results.push(matchData); >> // Found results, so continue searching. >> >> // Aggregate our new promise into our collection of >> promises. >> // Add our previously-created promise here. >> // * Note 1 >> var allDonePromise = Promise.all([allDonePromise, >> recurseSearch(text, results)]); >> } >> else { >> allDonePromise.resolve(matchData); >> } >> }, >> function(error) { >> allDonePromise.reject('There was a problem getting results'); >> } >> >> return allDonePromise; >> } >> >> >> var rf = new FindText({ text: "Rage, rage" }); >> recurseSearch(rf).then(function(results) { >> console.log(results); >> }); >> >> >> * Note 1 >> Our promise collection looks odd here. You've got a promise object that >> looks like a lopsided tree: >> >> [ Promise 1, >> [ Promise 2, >> [Promise 3, >> [Promise 4, >> etc... >> ] >> ] >> ] >> ] >> >> Which will eventually resolve itself. Not exactly performant, or >> readable. >> >> ... >> >> The problem, briefly, is that you end up with recursion when you try to >> find all: >> >> Search 1 -> (returns S1.promise) >> Search 2 -> (appends S2.promise to S1.promise) >> Search 3 -> (appends S3.promise to S1.promise and S2.promise) >> done, resolve S1.promise && S2.promise && S3.promise altogether. >> >> You cannot simply chain promises here in the normal fashion >> (.then().then().then() etc) because we do not know how many promises we'll >> end up with in the end. We have no idea how deep the thread goes, we must >> simply wait for the last one to return the whole stack of promises. That >> is effectively, the *first* promise is not resolved until the *last* search >> is done. >> >> Instead, in each step we must return a promise, which is added to the >> chain of promises to be resolve all at once. This is kind of messy. This >> also can lead users to make basic mistakes such as this one (the >> Promise.all method collects other promises into a single new promise that >> resolves when all are done) : >> >> var promise = Promise.all( >> rf.search(), >> rf.search(), >> rf.search() >> ).then(function( results ) { >> console.log(results); >> }); >> >> Where they will think they're getting the first three results, when in >> fact they will receive three copies of the first result, because they >> happen simultaneously. >> >> >> The simple solution is have a searchAll() method, that returns a promise >> that gets all results. A great addition to this is to provide a limit >> argument, which only finds the first N results and then returns. Those >> three options (find one, find all, find N) should account for the majority >> of use cases nicely, and will provide a single familiar interface for >> users. Given that, Example 1 becomes much nicer: >> >> >> Without promises, get the third (original example): >> >> var rf = new FindText({ text: "Rage, rage" }); >> var result = rf.search(); // result is 1st instance of string >> result = rf.search(); // result is 2nd instance of string >> result = rf.search(); // result is 3rd instance of string, the target >> instance >> >> get all: >> >> var rf = new FindText({ text: "Rage, rage" }); >> var results = []; >> while( var result = rf.search() ) { >> results.push(result); >> } >> >> get 3: >> >> var rf = new FindText({ text: "Rage, rage" }); >> var results = []; >> results.push( rf.search() ); // result is 1st instance of string >> results.push( rf.search() ); // result is 2nd instance of string >> results.push( rf.search() ); // result is 3rd instance of string >> >> >> >> With promises and searchAll, get the third: >> >> var rf = new FindText({ text: "Rage, rage" }); >> var promise = rf.searchAll(3); >> promise.then( function( results ) { >> console.log( results[2] ); >> } ); >> >> get all: >> >> var rf = new FindText({ text: "Rage, rage" }); >> var promise = rf.searchAll(); >> promise.then( function(results) { >> console.log(results); >> }); >> >> get 3: >> >> var rf = new FindText({ text: "Rage, rage" }); >> var promise = rf.searchAll(3); >> promise.then( function( results ) { >> console.log( results ); >> } ); >> >> Much cleaner than my previous example, obviously! Here's a good >> description of promises that shows how they should be used, and covers the >> philosophy a bit better than most tutorials: >> >> https://blog.domenic.me/youre-missing-the-point-of-promises/ >> >> >> Bill Hunt >> Senior Developer >> OpenGov Foundation >> http://opengovfoundation.org/ >> >> Ph: 20-BILL-HUNT >> 202 455 4868 >> bill@opengovfoundation.org >> >> On Oct 6, 2015, at 10:47 AM, Ivan Herman <ivan@w3.org> wrote: >> >> Hey Doug, >> >> After a first read, I have two questions/comments. >> >> - (This is minor:) the idea of using an edit distance for suffix/prefix >> is great. However: the way you specify the (maximal) edit distance is >> through a number, ie, the number of editing steps. However, shouldn't this >> edit distance limit be expressed (or at least alternatively express) >> through a percentage of the editing distance over the size of the >> suffix/prefix? I mean: if the suffix is 4 characters long, then an edit >> distance of 3 is significant, whereas the same distance is insignificant if >> the suffix is 100 characters long. Would a percentage be a good alternative? >> >> - (This may be major, but may simply be a result of my own ignorance:) I >> have read about, and actually used in a simple setting, Promises, but they >> still twist my mind, I must admit. One thing that seems to be fairly >> complex when using Promises is when one has to create cycles using them, >> primarily when the number of steps in the cycle is unknown in advance. On >> the other hand, using the search() method in the current spec would require >> exactly that: you do some sort of an iterative go through the search >> results. Maybe there is an easy way to express that with promises which I >> simply do not know, but if this really is complex then what this tells me >> is that the searchAll() might become the method of choice (and one could >> then run a traditional cycle on the results). There are, obviously, >> performance issues, though. >> >> B.t.w., I believe that the example: >> >> var rf = new FindText({ text: "Rage, rage" }); >> var result = rf.search(); // result is 1st instance of string >> result = rf.search(); // result is 2nd instance of string >> result = rf.search(); // result is 3rd instance of string, the target >> instance >> >> would not work, exactly for this reason. Each rf.search() returns a >> Promise, ie, one has to use a rf.search().when(function{…}) pattern for >> each entry, and it is not clear in my mind how the iteration materializes >> in the code. >> >> Apologies if I am completely wrong in terms of these Promises... >> >> >> Cheers >> >> Ivan >> >> >> On 05 Oct 2015, at 21:03 , Doug Schepers <schepers@w3.org> wrote: >> >> Hi, folks– >> >> This weekend, I made substantial changes to the FindText API [1] >> (formerly called the RangeFinder API). >> >> I improved the internationalization aspects and options, based on >> feedback from the I18n WG and from their updated CharMod spec (Character >> Model for the World Wide Web: String Matching and Searching… which seems >> tailor-made for us!). >> >> I also fleshed out the algorithm for search (though it still needs lots >> of work), which was one of two critical changes needed before FPWD. >> >> The remaining critical change is for me to update the examples, which is >> important because those will shape many people's first impressions of the >> spec (because examples are easy to read and understand). This is my plan >> for the rest of the day. This involves describing the workflow in terms of >> Promises, which I'm sad to admit I've never used in running before. >> >> Luckily, I have two meetings set up for this afternoon with folks to help >> me with that: >> >> * Chris Birk and Bill Hunt, from OpenGov Foundation >> * Alexander Schmidtz, from jQuery >> >> These guys are very familiar with Promises, and so my examples and API >> design will have at least a bit of vetting and validation before pushing >> FPWD. There will always be room for improvements, but we should be ready to >> go by tomorrow. >> >> >> I welcome feedback from any of you on this spec! >> >> >> [1] http://w3c.github.io/findtext/ >> [2] http://w3c.github.io/charmod-norm/ >> >> Regards– >> –Doug >> >> >> >> ---- >> Ivan Herman, W3C >> Digital Publishing Lead >> Home: http://www.w3.org/People/Ivan/ >> mobile: +31-641044153 >> ORCID ID: http://orcid.org/0000-0003-0782-2704 >> >> >> >> >> >> >> >> ---- >> Ivan Herman, W3C >> Digital Publishing Lead >> Home: http://www.w3.org/People/Ivan/ >> mobile: +31-641044153 >> ORCID ID: http://orcid.org/0000-0003-0782-2704 >> >> >> >> >> > > > ---- > Ivan Herman, W3C > Digital Publishing Lead > Home: http://www.w3.org/People/Ivan/ > mobile: +31-641044153 > ORCID ID: http://orcid.org/0000-0003-0782-2704 > > > > >
Received on Wednesday, 7 October 2015 19:44:10 UTC