RE: DOM Storage feedback

> -----Original Message-----
> From: Ian Hickson [mailto:ian@hixie.ch]
> Sent: Friday, June 20, 2008 12:17 AM
> To: Zhenbin Xu
> Cc: Ian Hickson; public-html-comments@w3.org; public-html@w3.org;
> Sunava Dutta; IE8 Core AJAX SWAT Team
> Subject: RE: DOM Storage feedback
>
> On Thu, 19 Jun 2008, Zhenbin Xu wrote:
> > >
> > > The idea is that the storage is asynchronous (and under the control
> of
> > > the UA), but that the API appears synchronous. That is, as soon as
> one
> > > script sets a storage item to a particular value, the setter will
> > > return with no latency, and all attempts from any frames to read
> that
> > > same storage item will give the new value back. It doesn't have to
> be
> > > stored to disk immediately, however. Since this is a simple
> name/value
> > > text-only API, it is trivially cached in memory.
> >
> > Interesting - this is precisely what we meant by async model -
> storage
> > is asynchronous but the API appears synchronous.  Either the spec has
> > changed or we may have read the spec incorrectly? Previously the spec
> > stated that the setItem operation has to guarantee atomic operation,
> > which we read it to mean guaranteed to be on persist store (disk or
> > cloud).  If this is not the case, then we are in agreement here.
> Since
> > we thought we differed from the spec, we added the event to
> compensate
> > for it.  We don't feel it is important event but it may be useful.
>
> The spec does say things have to be atomic from the perspective of the
> scripts, but doesn't attempt to say anything about how that should be
> implemented. Would you like this made more explicit?
>
>

[Zhenbin Xu] More explicit would be nice - especially the synchronous API
but asynchronous storage part.



> > > Also, if the main use case is caching data, then it is reasonably
> easy
> > > to use the simple API to provide a transaction-like mechanism:
> > >
> > >    // run this first, in one script block
> > >    var id = localStorage['last-id'] + 1;
> > >    localStorage['last-id'] = id;
> > >    localStorage['email-ready-' + id] = "0"; // "begin"
> > >
> > >    // these can run each in separate script blocks as desired
> > >    localStorage['email-subject-' + id] = subject;
> > >    localStorage['email-from-' + id] = from;
> > >    localStorage['email-to-' + id] = to;
> > >    localStorage['email-body-' + id] = body;
> > >
> > >    // run this last
> > >    localStorage['email-ready-' + id] = "1"; // "commit"
> > >
> > > An author could easily wrap this up providing custom begin() and
> > > commit() functions if desired. I don't see what having them in the
> API
> > > gains us, really, other than extra complexity for UAs.
> >
> > Here is the concern:
> >
> >      localStorage['email-subject-' + id] = getSubject();
> >      localStorage['email-from-' + id] = resolveEmailias(from);
> >      localStorage['email-to-' + id] = resolveEmailalis(to);
> >      localStorage['email-body-' + id] = getBody();
> >
> > If resolveEmailAlias throws an exception, we would have partially
> > persisted states. Yes you can work around it by assigning results to
> > temporary variables but it is simpler to provide begin() and commit()
> to
> > the page author, especially considering there can be complex logic
> > between states. E.g
> >
> >      localStorage.begin();
> >      Component1FromDeveloper1();     // local storage operation
> inside
> >      Component2FromDeveloper2();     // local storage operation
> inside
> >      localStorage.commit();
> >
> > Having begin/commit would allow better distributed, componentized
> > development.
>
> But the problem is that the above would fail if the component from
> developer 2 uses .begin() or .commit(),


[Zhenbin Xu] This issue is commonly solved by strict coding guideline.
It is the integrator's job to ensure state integrity.  Developers
should not be handing begin/commit etc. inside their component.


 or if an exception was raised
> and
> caught elsewhere (since the .commit() would be missed and now
> everything
> would be covered by the "transaction"), etc. Basically having just a
> single transaction state with two separate methods and nothing based on
> an
> object's lifetime or anything to guarantee consistency the API becomes
> unusable in any number of ways.
>

[Zhenbin Xu] The UA should make sure transaction is rolled back in case of exception.
Alternatively, it would seem fitting to have a rollback() API here

try
{
        localStorage.begin();
        ....
        localStorage.commit();
}
catch
{
        localStorage.rollback();
}




> Right now you can avoid all these problems by just doing:
>
>    var success = false;
>    try {
>      var id = localStorage['last-id'] + 1;
>      localStorage['last-id'] = id;
>      localStorage['email-ready-' + id] = "0"; // "begin"
>
>      localStorage['email-subject-' + id] = getSubject();
>      localStorage['email-from-' + id] = resolveEmailAlias(from);
>      localStorage['email-to-' + id] = resolveEmailAlias(to);
>      localStorage['email-body-' + id] = getBody();
>
>      success = true;
>    } catch (e) {
>      // report or resolve e if necessary


[Zhenbin Xu]  Not easy for script recover from exception. Either some garbage
are left in localStorage, or a more complex scheme is needed.



>    }
>    if (success) {
>      localStorage['email-ready-' + id] = "1"; // "commit"
>    } else {
>      localStorage.removeItem('email-subject-' + id);
>      localStorage.removeItem('email-from-' + id);
>      localStorage.removeItem('email-to-' + id);
>      localStorage.removeItem('email-body-' + id);
>      localStorage.removeItem('email-ready-' + id); // "cancel"
>    }
>
> This can easily be wrapped into a utility method that takes a single
> method as its only argument:
>
>    function transactionAdd(body) {
>      var success = false;
>      var keys = [];
>      try {
>        var id = localStorage['last-id'] + 1;


[Zhenbin Xu] If the id is persisting on disk and ever increasing,
there is a danger of overflow. So a more elaborated scheme may be
needed here.


>        localStorage['last-id'] = id;
>        localStorage['block-' + id] = "0"; // "begin"
>
>        body(function (name, value) {
>          localStorage['block-' + id + '-' + name] = value;
>          keys.push(name);
>        });
>
>        success = true;
>      } catch (e) {
>        // report or resolve e if necessary

[Zhenbin Xu] It is difficult to do any meaningful error handling
inside an utility function since it lacks context. Better leave
it to the callers who have more information about the situation.


>      }
>      if (success) {
>        localStorage['block-' + id] = "1"; // "commit"
>      } else {
>        for (var i in keys)
>          localStorage.removeItem('block-' + id + '-' + keys[i]);
>        localStorage.removeItem('block-' + id); // "cancel"
>      }
>      return success;
>    }
>
> You would use this like this:
>
>    transactionAdd(function(add) {
>      add('subject', getSubject());
>      add('from', resolveEmailAlias(from));
>      add('to', resolveEmailAlias(to));
>      add('body', getBody());
>    });
>

[Zhenbin Xu] How would application retrieve particular item later given
that it has no knowledge of what block id is?

If a wrapper function is needed all the time, why not make it as part
of the platform?



> In short I don't really see the need for an explicit .begin() or
> .commit()
> feature in the API.
>




>
> (Also, I'd like to reiterate my earlier request that the non-standard
> event and methods that you implemented in IE8 either be removed or be
> clearly prefixed with "ms" or some such to indicate that they are
> proprietary extensions -- we don't want to confuse authors on the
> platform
> who might test in one browser and find things don't work in another
> browser. Thanks!)
>
> --
> Ian Hickson               U+1047E                )\._.,--....,'``.
> fL
> http://ln.hixie.ch/       U+263A                /,   _.. \   _\  ;`._
> ,.
> Things that are impossible just take longer.   `._.-(,_..'--(,_..'`-
> .;.'

Received on Friday, 20 June 2008 18:13:12 UTC