【问题标题】:delay for jquery promisesjquery 承诺的延迟
【发布时间】:2015-08-25 03:40:01
【问题描述】:

我找不到jQuery 承诺的delaywait 函数。我在 SO (Using jQuery.Deferred to avoid nested setTimeout callbacks) 上找到了一个函数:

function delay(time) {
    return function () {
        console.log("Delaying");
        var ret = new $.Deferred();
        setTimeout(function () {
            ret.resolve();
        }, time);
        return ret;
    };
}

而且,这就是我使用它的方式:

 run: function () {
        return $()
            .promise()
            .then(function () {
                console.log("call together");
                console.log("call together");    
            })
            .then(delay(2000))
            .then(function () {
                console.log("call first");
            })
            .then(delay(2000))
            .then(function () {
                console.log("call second");
            })
    }

我想扩展我可以写的承诺或延迟对象:

run: function () {
            return $()
                .promise()
                .then(function () {
                    console.log("call together");
                    console.log("call together");    
                })
                .delay(2000)
                .then(function () {
                    console.log("call first");
                })
                .delay(2000)
                .then(function () {
                    console.log("call second");
                })
        }

【问题讨论】:

  • @guest271314:我需要完全符合承诺的功能。这个函数用于效果。
  • Is it possible to add methods to JQuery's promise object? 的可能重复项(只需添加 .delay 方法而不是 .catch
  • @jfriend00:当然是it is possible……但不像扩展原型那么容易。
  • @jfriend00:该代码适用于在装饰器代码运行后构建的所有延迟和承诺(包括 jqXHR)。但是,如果您还有其他问题,让我们在那里讨论:-)

标签: jquery promise jquery-deferred


【解决方案1】:

正如@Bergi 所说,jQuery Deferreds/Promises 不能通过原型继承来扩展。

相反,jQuery 采用的模型是允许使用以下语法扩展单个 Promise 实例:

deferred.promise(target);
//or, 
promise.promise(target); //(though the documentation doesn't make this clear)
// where `target` is an "object onto which the promise methods have to be attached"
// see https://api.jquery.com/deferred.promise/

通过定义一个带有一堆方法的构造函数,任何 jQuery Deferred 或 Promise 都可以用简单的语法进行扩展

.promise(Constructor())

在我未发布的、未记录的 jQuery promises Playground 中,构造函数名为 $P 并保存在 jQuery 命名空间中,因此我使用的实际语法是:

.promise($.$P())

您需要注意,在大多数情况下,没有必要显式调用 $.$P(),因为 Playground 包含一个返回已扩展 Promise 的 $.when_() 方法。

这是 Playground 的缩写版本,仅提供 .delay() 方法即可:

(function($) {
    /* ***********************************
     * The $.$P function returns an object
     * designed to be extended with 
     * promise methods using the syntax :
     *    myDeferred.promise($.$P())
     *    myPromise.promise($.$P())
     * where `myDeferred`/`myPromise` 
     * are jQuery Deferred/Promise objects.
     * ***********************************/

    /* ***********************************
     * Methods
     * ***********************************/
    $.$P = function() {
        if (this instanceof $.$P) {
            return this;
        } else {
            return new $.$P();
        }
    };
    $.$P.prototype.then_ = function(fa, fb) {
        /* A promise method that is the same as .then()
         * but makes these extra methods available 
         * down-chain.
         */
        return this.then(fa||null, fb||null).promise($.$P());
    }
    $.$P.prototype.delay_ = function(ms) {
        /* A promise method that 
         * introduces a down-chain delay.
         */
        var promise = this;
        function f(method) {
            return function() { setTimeout(function(){ method.apply(null,this); }.bind(arguments), ms||0); };
        }
        return $.Deferred(function(dfrd) { 
            promise.then(f(dfrd.resolve), f(dfrd.reject));
        }).promise($.$P());
    }

    /* ***********************************
     * Utility functions
     * ***********************************/
    function consolidate(args) {
        /* Convert mixed promises/arrays_of_promises to single array.
         * Called by all the when_() methods below.
         */
        return Array.prototype.slice.apply(args).reduce(function(arr, current) {
            return arr.concat(current);
        }, []);
    }

    /* ***********************************
     * This section extends the jQuery namespace 
     * with a "jQuery.when_()" method.
     * ***********************************
     */
    $.extend({
        'when_': function() {
            return $.when.apply(null, consolidate(arguments)).promise($.$P()).then_(function() {
                return consolidate(arguments);
            });
        },
    });
})(jQuery);

完整的 Playground 还包括更多用于其他目的的静态和 promise-instance 方法,而开发它们是游戏的精髓。

使用 Playgound 的基本规则如下:

  • Playground 的所有静态和承诺方法都以“_”下划线结尾。
  • 只需安装 Playgound 即可使用静态方法,例如 $.when_()
  • Promise 链中的 Promise 通过包含静态方法(例如 .when_() 或链接 .promise($.$P()))进行扩展。
  • 在promise 链中,通过使用“..._”方法而不是标准方法(例如.then_() 代替.then()),扩展仍然可用(在链下游)。

所以这里是如何使用它来施加问题所需的延迟:

jQuery(function($) {
    var MYNAMESPACE = {
        run: function (t) {
            return $.when_()
            .then_(function () {
                log("call together");
                log("call together");    
            })
            .delay_(t)
            .then_(function () {
                log("call first");
            })
            .delay_(t)
            .then_(function () {
                log("call second");
            });
        }
    }
});

DEMO

在演示中,按钮的点击处理程序进一步说明了如何使用 Playground。

使用游乐场的限制条件:

  • 正如我所说 - 这是一个游乐场
  • 作为 jQuery 的适配器,而不是补丁,它在某些地方效率极低。最糟糕的方面是,一些方法在返回的承诺之外还创建了一个中间承诺。
  • 未根据生产代码所需的标准进行测试,因此请谨慎使用。

最后,如果你确定要使用 jQuery 实现延迟,请仅考虑上述内容。使用已经有 .delay() 方法的 Promise 库要简单得多。

【讨论】:

  • 你是承诺大师。谢谢。
【解决方案2】:

编辑、更新

尝试将属性delay 添加到jQuery.Deferred

    delay: function(t) {
      return this.then(function() {
        var args = arguments;
        return new $.Deferred(function(d) {
          setTimeout(function() {
            // return `data`, if any, to next method, e.g., `.then`, in chain
            d.resolveWith(this, args)
          }.bind(this), t || 0)
        }).promise()
      })
    }

(function($) {
  $.Deferred = function(a) {
    var b = [
        ["resolve", "done", $.Callbacks("once memory"), "resolved"],
        ["reject", "fail", $.Callbacks("once memory"), "rejected"],
        ["notify", "progress", $.Callbacks("memory")]
      ],
      c = "pending",
      d = {
        delay: function(t) {
          return this.then(function() {
            var args = arguments;
            return new $.Deferred(function(d) {
              setTimeout(function() {
                // return `data`, if any, to next method, e.g., `.then`, in chain
                d.resolveWith(this, args)
              }.bind(this), t || 0)
            }).promise()
          })
        },
        state: function() {
          return c
        },
        always: function() {
          return e.done(arguments).fail(arguments), this
        },
        then: function() {
          var a = arguments;
          return $.Deferred(function(c) {
            $.each(b, function(b, f) {
              var g = $.isFunction(a[b]) && a[b];
              e[f[1]](function() {
                var a = g && g.apply(this, arguments);
                a && $.isFunction(a.promise) 
                ? a.promise()
                  .done(c.resolve)
                  .fail(c.reject)
                  .progress(c.notify) 
                : c[f[0] + "With"](this === d 
                  ? c.promise() 
                  : this, g ? [a] : arguments)
              })
            }), a = null
          }).promise()
        },
        promise: function(a) {
          return null != a ? $.extend(a, d) : d
        }
      },
      e = {};
    return d.pipe = d.then, $.each(b, function(a, f) {
      var g = f[2],
        h = f[3];
      d[f[1]] = g.add, h && g.add(function() {
        c = h
      }, b[1 ^ a][2].disable, b[2][2].lock), e[f[0]] = function() {
        return e[f[0] + "With"](this === e ? d : this, arguments), this
      }, e[f[0] + "With"] = g.fireWith
    }), d.promise(e), a && a.call(e, e), e
  }

}(jQuery));

var p = {
  run: function() {
    return $()
      .promise()
      .then(function() {
        console.log("call together");
        console.log("call together");
        // do stuff
        // pass `data` to next `.then`
        return "call first";
      })
      .delay(2000)
      .then(function(data) {
        console.log(data);
      })
      .delay(2000)
      .then(function() {
        console.log("call second");
      })
  }
};

p.run();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>

【讨论】:

  • OP 对他想要写的内容提出了非常具体的要求。你的回答不符合这一点。在我看来,这比他使用.then(delay(2000)) 的工作解决方案还要糟糕
  • 我想是的,是的,尽管我不会考虑用不同的实现完全替换 $.Deferred 是一个好的解决方案。如果你打算使用resolveWith,那么你可能应该从then回调中传递原始的thisarguments,而不仅仅是第一个参数。
  • @Bergi 解决方案不会尝试用不同的实现替换$.Deferred;除了delay,@ 987654334@ at post 是来自版本jQuery "1.11.2" 的$.Deferred 的来源。是的,同意通过原始 thisarguments 。最初测试$.Deferred() 如果在delay 内调用,是否确实会返回delay;初始尝试允许在第一次调用时定义.delay,然后在第二次调用时定义undefined,链接到第一个方法。顺便说一句,“我觉得这比他使用.then(delay(2000)) 的工作解决方案更糟糕” 确实激发了“遵守”谢谢!查看更新后的帖子
  • 是的,它用 same 实现覆盖了$.Deferred - 我认为复制粘贴编程是一个问题,不一定是从功能的角度,而是从可维护性的角度来看。
【解决方案3】:

这是我的解决方案。我包装$.Deferred(afterBuild) 并包装原始afterBuild,然后包装.promise(obj) 方法,扩展给定obj 与自定义delay 方法。其中使用window.setTimeout

注意:它只会延迟done 分支。

function extendPromises(extensions) {
    $.Deferred = (function (originalDeferred) {
        return function (afterBuild) {
            var newAfterBuild = function (d) {
                d.promise = (function (originalPromise) {
                    return function (obj) {
                        return originalPromise.call(this, $.extend(obj, extensions));
                    };
                })(d.promise);
                if (afterBuild) afterBuild.apply(this, arguments);
                return this;
            };
            return originalDeferred.call(this, newAfterBuild);
        };
    })($.Deferred);
}

extendPromises({
    delay: function (delay) {
        return this.then(function (value) {
            var d = $.Deferred();
            window.setTimeout(function () {
                d.resolve(value);
            }, delay);
            return d.promise();
        });
    }
});

// so now I can do:
$.when("hello")
.then(function (value) { $("#log").append(value+"\n"); return value; })
.delay(1000)
.then(function (value) { $("#log").append(value); return value; });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea id="log" rows=5></textarea>

【讨论】:

    猜你喜欢
    • 2015-03-27
    • 2023-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-10
    • 2019-04-26
    相关资源
    最近更新 更多