[wp-trac] [WordPress Trac] #38889: Proposition to allow an api.Value() using asynchronous callbacks

WordPress Trac noreply at wordpress.org
Mon Nov 21 17:22:11 UTC 2016


#38889: Proposition to allow an api.Value() using asynchronous callbacks
-------------------------------+------------------------------
 Reporter:  nikeo              |       Owner:
     Type:  enhancement        |      Status:  new
 Priority:  normal             |   Milestone:  Awaiting Review
Component:  Customize          |     Version:  trunk
 Severity:  normal             |  Resolution:
 Keywords:  reporter-feedback  |     Focuses:  javascript
-------------------------------+------------------------------
Changes (by westonruter):

 * keywords:   => reporter-feedback


Old description:

> When an `api.Value` instance is set to a new value, we might need to wait
> for all callbacks to be completed or resolved before actually doing
> something else.
> With the current implementation, if an `api.Value` has asynchronous
> callbacks, there's no way to know when it's resolved after a `set()`
> action. Any further dependant actions would have to be done after an
> hazardous delay.
>
> The following is a proposition to solve this by adding an optional `{
> deferred : true }` param when binding a callback to an `api.Value`
> instance.
>
> This way, a syntax like this could be used :
>
> {{{
>
> //let's declare a new api Value().
> api.observable_value = new api Value( 'initial' );
>
> //this is basic callback with a $.Deferred callback
> var fakeAjaxCall = function( to, from ) {
>     var dfd = $.Deferred();
>
>     _.delay( function() {
>         console.log( 'I am a fake ajax call.');
>         dfd.resolve( to );
>     }, 2000 );
>
>     return dfd.promise();
> };
>
> //let's bind the Value instance with this callback and inform it that it
> is a deferred action
> api.observable_value.bind( fakeAjaxCall, {deferred : true } );
>
> //now let's set update the value
> api.observable_value.set( 'new_val' ).done( function() {
>     //do something with this new_val when all callbacks are done
>     console.log('All callbacks are done now, we can use the new val in
> further dependant actions : ', this() );
> });
> }}}
>

> This could be useful for callbacks involving preview refreshs ( if the
> preview returns a `promise()` , see #38797), or media fetched in ajax in
> db.
>

> '''Code'''
> This is a proposition of modification of the `api.Value` constructor, in
> the current customize-base.js file. ( as of 4.7 beta-3 )
>
> The code introduces an optional additional collection of `$.Deferred`
> functions, as a property of the `api.Value` constructor.
>

> {{{
> /**
>  * Set the value and trigger all bound callbacks.
>  * @return a promise onto which it is attached the current 'this' of the
> api.Value, when all callbacks are resolved.
>  * @param {object} to New value.
>  */
> set: function( to ) {
>     var from = this._value, dfd = $.Deferred(), self = this, _promises =
> [];
>
>     to = this._setter.apply( this, arguments );
>     to = this.validate( to );
>
>     // Bail if the sanitized value is null or unchanged.
>     if ( null === to || _.isEqual( from, to ) ) {
>       return this;
>     }
>
>     this._value = to;
>     this._dirty = true;
>
>     if ( this._deferreds ) {
>           _.each( self._deferreds, function( _prom ) {
>                 _promises.push( _prom.apply( null, [ to, from ] ) );
>           });
>
>           $.when.apply( null, _promises )
>                 .then( function() {
>                       self.callbacks.fireWith( self, [ to, from ] );
>                       dfd.resolveWith( self, [ to, from ] );
>                 });
>     } else {
>           this.callbacks.fireWith( this, [ to, from ] );
>           return dfd.resolveWith( self, [ to, from ] ).promise( self );
>     }
>     return dfd.promise( self );
> }
>

> /**
>  * Bind a function to be invoked whenever the value changes.
>  *
>  * @param {...Function} A function, or multiple functions, to add to the
> callback stack.
>  * @param { deferred : true } let us decide if the callback(s) have to be
> populated as $.Callbacks or $.Deferred
>  */
> bind: function() {
>   var self = this,
>       _isDeferred = false,
>       _cbs = [];
>
>   $.each( arguments, function( _key, _arg ) {
>         if ( ! _isDeferred )
>           _isDeferred = _.isObject( _arg  ) && _arg.deferred;
>         if ( _.isFunction( _arg ) )
>           _cbs.push( _arg );
>   });
>
>   if ( _isDeferred ) {
>         self._deferreds = self._deferreds || [];
>         _.each( _cbs, function( _cb ) {
>               if ( ! _.contains( _cb, self._deferreds ) )
>                 self._deferreds.push( _cb );
>         });
>   } else {
>         //original method
>         self.callbacks.add.apply( self.callbacks, arguments );
>   }
>   return this;
> }
> }}}
>
> The `unbind` method of the `api.Value` constructor is missing here, it
> should also be modified.
> Any thoughts or feedbacks are welcome !

New description:

 When an `api.Value` instance is set to a new value, we might need to wait
 for all callbacks to be completed or resolved before actually doing
 something else.
 With the current implementation, if an `api.Value` has asynchronous
 callbacks, there's no way to know when it's resolved after a `set()`
 action. Any further dependant actions would have to be done after an
 hazardous delay.

 The following is a proposition to solve this by adding an optional `{
 deferred : true }` param when binding a callback to an `api.Value`
 instance.

 This way, a syntax like this could be used :

 {{{#!js

 //let's declare a new api Value().
 api.observable_value = new api Value( 'initial' );

 //this is basic callback with a $.Deferred callback
 var fakeAjaxCall = function( to, from ) {
     var dfd = $.Deferred();

     _.delay( function() {
         console.log( 'I am a fake ajax call.');
         dfd.resolve( to );
     }, 2000 );

     return dfd.promise();
 };

 //let's bind the Value instance with this callback and inform it that it
 is a deferred action
 api.observable_value.bind( fakeAjaxCall, {deferred : true } );

 //now let's set update the value
 api.observable_value.set( 'new_val' ).done( function() {
     //do something with this new_val when all callbacks are done
     console.log('All callbacks are done now, we can use the new val in
 further dependant actions : ', this() );
 });
 }}}


 This could be useful for callbacks involving preview refreshs ( if the
 preview returns a `promise()` , see #38797), or media fetched in ajax in
 db.


 '''Code'''
 This is a proposition of modification of the `api.Value` constructor, in
 the current customize-base.js file. ( as of 4.7 beta-3 )

 The code introduces an optional additional collection of `$.Deferred`
 functions, as a property of the `api.Value` constructor.


 {{{#!js
 /**
  * Set the value and trigger all bound callbacks.
  * @return a promise onto which it is attached the current 'this' of the
 api.Value, when all callbacks are resolved.
  * @param {object} to New value.
  */
 set: function( to ) {
     var from = this._value, dfd = $.Deferred(), self = this, _promises =
 [];

     to = this._setter.apply( this, arguments );
     to = this.validate( to );

     // Bail if the sanitized value is null or unchanged.
     if ( null === to || _.isEqual( from, to ) ) {
       return this;
     }

     this._value = to;
     this._dirty = true;

     if ( this._deferreds ) {
           _.each( self._deferreds, function( _prom ) {
                 _promises.push( _prom.apply( null, [ to, from ] ) );
           });

           $.when.apply( null, _promises )
                 .then( function() {
                       self.callbacks.fireWith( self, [ to, from ] );
                       dfd.resolveWith( self, [ to, from ] );
                 });
     } else {
           this.callbacks.fireWith( this, [ to, from ] );
           return dfd.resolveWith( self, [ to, from ] ).promise( self );
     }
     return dfd.promise( self );
 }


 /**
  * Bind a function to be invoked whenever the value changes.
  *
  * @param {...Function} A function, or multiple functions, to add to the
 callback stack.
  * @param { deferred : true } let us decide if the callback(s) have to be
 populated as $.Callbacks or $.Deferred
  */
 bind: function() {
   var self = this,
       _isDeferred = false,
       _cbs = [];

   $.each( arguments, function( _key, _arg ) {
         if ( ! _isDeferred )
           _isDeferred = _.isObject( _arg  ) && _arg.deferred;
         if ( _.isFunction( _arg ) )
           _cbs.push( _arg );
   });

   if ( _isDeferred ) {
         self._deferreds = self._deferreds || [];
         _.each( _cbs, function( _cb ) {
               if ( ! _.contains( _cb, self._deferreds ) )
                 self._deferreds.push( _cb );
         });
   } else {
         //original method
         self.callbacks.add.apply( self.callbacks, arguments );
   }
   return this;
 }
 }}}

 The `unbind` method of the `api.Value` constructor is missing here, it
 should also be modified.
 Any thoughts or feedbacks are welcome !

--

Comment:

 @nikeo I'm not sure I understand. In your example here:

 {{{#!js
 api.observable_value.bind( fakeAjaxCall, {deferred : true } );

 api.observable_value.set( 'new_val' ).done( function() {
     //do something with this new_val when all callbacks are done
     console.log('All callbacks are done now, we can use the new val in
 further dependant actions : ', this() );
 });
 }}}

 In this case, the value of “new_val” is basically temporary throw-away,
 isn't it? You're ultimately wanting to get the value from an Ajax request?
 It seems to me that you should rather just call `fakeAjaxCall` directly
 and let it set the value once. What is the use case you're looking at?

--
Ticket URL: <https://core.trac.wordpress.org/ticket/38889#comment:2>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform


More information about the wp-trac mailing list