发布在:代码组织 > 延迟

延迟示例

链接 更多延迟示例

延迟在 Ajax 中后台使用,但这并不意味着它们不能在其他地方使用。本部分描述了延迟将有助于抽象异步行为并解耦我们代码的情况。

链接 缓存

链接 异步缓存

当涉及到异步任务时,缓存可能有点要求苛刻,因为你必须确保某个给定键的任务只执行一次。因此,代码必须以某种方式跟踪入站任务。

1
2
$.cachedGetScript( url, callback1 );
$.cachedGetScript( url, callback2 );

缓存机制必须确保即使脚本尚未缓存,也只请求一次 URL。这展示了一些逻辑来跟踪绑定到给定 URL 的回调,以便缓存系统正确处理已完成和入站请求。

1
2
3
4
5
6
7
8
9
10
var cachedScriptPromises = {};
$.cachedGetScript = function( url, callback ) {
if ( !cachedScriptPromises[ url ] ) {
cachedScriptPromises[ url ] = $.Deferred(function( defer ) {
$.getScript( url ).then( defer.resolve, defer.reject );
}).promise();
}
return cachedScriptPromises[ url ].done( callback );
};

每个 URL 缓存一个 Promise。如果给定 URL 还没有 Promise,则创建一个延迟并发出请求。当请求完成时,延迟被解决(使用 defer.resolve);如果发生错误,则延迟被拒绝(使用 defer.reject)。如果 Promise 已存在,则回调附加到现有延迟;否则,首先创建 Promise,然后附加回调。此解决方案的一大优点是它将透明地处理已完成和入站请求。另一个优点是基于延迟的缓存将优雅地处理故障。Promise 最终会被拒绝,可以通过提供错误回调来测试它

1
$.cachedGetScript( url ).then( successCallback, errorCallback );

链接 通用异步缓存

还可以使代码完全通用,并构建一个缓存工厂,该工厂将抽象出当键不在缓存中时要执行的实际任务

1
2
3
4
5
6
7
8
9
10
11
$.createCache = function( requestFunction ) {
var cache = {};
return function( key, callback ) {
if ( !cache[ key ] ) {
cache[ key ] = $.Deferred(function( defer ) {
requestFunction( defer, key );
}).promise();
}
return cache[ key ].done( callback );
};
};

现在请求逻辑已被抽象出来,$.cachedGetScript() 可以重写如下

1
2
3
$.cachedGetScript = $.createCache(function( defer, url ) {
$.getScript( url ).then( defer.resolve, defer.reject );
});

这将起作用,因为每次调用 $.createCache() 都会创建一个新的缓存存储库并返回一个新的缓存检索函数。

链接 图像加载

可以使用缓存来确保不会多次加载同一图像。

1
2
3
4
5
6
7
8
9
10
11
12
$.loadImage = $.createCache(function( defer, url ) {
var image = new Image();
function cleanUp() {
image.onload = image.onerror = null;
}
defer.then( cleanUp, cleanUp );
image.onload = function() {
defer.resolve( url );
};
image.onerror = defer.reject;
image.src = url;
});

同样,以下代码段

1
2
$.loadImage( "my-image.png" ).done( callback1 );
$.loadImage( "my-image.png" ).done( callback2 );

无论 my-image.png 是否已加载或是否实际正在加载,都将起作用。

链接 缓存数据 API 响应

在您页面生命周期内被视为不可变的 API 请求也是完美的候选对象。例如,以下

1
2
3
4
5
6
7
8
9
10
11
$.searchTwitter = $.createCache(function( defer, query ) {
$.ajax({
url: "http://search.twitter.com/search.json",
data: {
q: query
},
dataType: "jsonp",
success: defer.resolve,
error: defer.reject
});
});

将允许您在 Twitter 上执行搜索并同时缓存它们

1
2
$.searchTwitter( "jQuery Deferred", callback1 );
$.searchTwitter( "jQuery Deferred", callback2 );

链接 计时

此基于延迟的缓存不仅限于网络请求;它还可用于计时目的。

例如,您可能需要在一段时间后在页面上执行操作,以便吸引用户注意他们可能不知道的特定功能或处理超时(例如,对于测验问题)。虽然 setTimeout() 适用于大多数用例,但它无法处理即使在理论上已过期的计时器被稍后请求的情况。我们可以使用以下缓存系统来处理这种情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var readyTime;
$(function() {
readyTime = jQuery.now();
});
$.afterDOMReady = $.createCache(function( defer, delay ) {
delay = delay || 0;
$(function() {
var delta = $.now() - readyTime;
if ( delta >= delay ) {
defer.resolve();
} else {
setTimeout( defer.resolve, delay - delta );
}
});
});

新的 $.afterDOMReady() 帮助程序方法在 DOM 准备就绪后提供适当的计时,同时确保使用最少的计时器。如果延迟已过期,任何回调都将立即调用。

链接 一次性事件

虽然 jQuery 提供了所有可能需要的事件绑定,但处理仅应处理一次的事件可能会变得有点麻烦。

例如,您可能希望有一个按钮,它将在第一次单击时打开一个面板,然后将其保持打开状态,或者在第一次单击该按钮时执行特殊的初始化操作。在处理这种情况时,人们通常会得到如下代码

1
2
3
4
5
6
7
8
9
var buttonClicked = false;
$( "#myButton" ).click(function() {
if ( !buttonClicked ) {
buttonClicked = true;
initializeData();
showPanel();
}
});

然后,稍后,您可能希望采取措施,但仅当面板打开时

1
2
3
4
5
if ( buttonClicked ) {
// Perform specific action
}

这是一个非常耦合的解决方案。如果您想添加其他操作,则必须编辑绑定代码或全部复制。如果您不这样做,您唯一的选择是测试 buttonClicked,并且您可能会丢失该新操作,因为 buttonClicked 变量可能是 false,并且您的新代码可能永远不会执行。

我们可以使用延迟对象做得更好(为了简化起见,以下代码仅适用于单个元素和单个事件类型,但可以轻松地将其概括为具有多个事件类型的成熟集合)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$.fn.bindOnce = function( event, callback ) {
var element = $( this[ 0 ] ),
defer = element.data( "bind_once_defer_" + event );
if ( !defer ) {
defer = $.Deferred();
function deferCallback() {
element.unbind( event, deferCallback );
defer.resolveWith( this, arguments );
}
element.bind( event, deferCallback )
element.data( "bind_once_defer_" + event , defer );
}
return defer.done( callback ).promise();
};

代码的工作原理如下

  • 检查元素是否已为给定事件附加延迟
  • 如果没有,则创建它并使其在事件第一次触发时得到解决
  • 然后将给定的回调附加到延迟并返回承诺

虽然代码肯定更冗长,但它以模块化和解耦的方式使处理手头问题变得更加简单。但让我们首先定义一个帮助器方法

1
2
3
$.fn.firstClick = function( callback ) {
return this.bindOnce( "click", callback );
};

然后可以重新调整逻辑,如下所示

1
2
3
4
var openPanel = $( "#myButton" ).firstClick();
openPanel.done( initializeData );
openPanel.done( showPanel );

如果某个操作应该仅在稍后打开面板时执行

1
2
3
4
5
openPanel.done(function() {
// Perform specific action
});

如果面板尚未打开,则不会丢失任何内容,该操作将仅推迟到单击按钮为止。

link 组合帮助器

单独来看,以上所有示例似乎都有点局限性。但是,当您将它们混合在一起时,承诺的真正力量就发挥作用了。

link 在第一次单击时请求面板内容并打开所述面板

以下是按钮的代码,单击该按钮时,将打开一个面板。它通过网络请求其内容,然后淡入内容。使用前面定义的帮助器,可以将其定义为

1
2
3
4
5
6
7
8
9
$( "#myButton" ).firstClick(function() {
var panel = $( "#myPanel" );
$.when(
$.get( "panel.html" ),
panel.slideDownPromise()
).done(function( ajaxResponse ) {
panel.html( ajaxResponse[ 0 ] ).fadeIn();
});
});

link 在第一次单击时在面板中加载图像并打开所述面板

另一个可能的目标是在单击按钮并加载所有图像后才使面板淡入。

此 HTML 代码看起来像

1
2
3
4
5
6
<div id="myPanel">
<img data-src="image1.png" alt="">
<img data-src="image2.png" alt="">
<img data-src="image3.png" alt="">
<img data-src="image4.png" alt="">
</div>

我们使用 data-src 属性来跟踪真实图像位置。使用我们的承诺帮助器处理我们的用例的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$( "#myButton" ).firstClick(function() {
var panel = $( "#myPanel" ),
promises = [];
panel.find( "img" ).each(function() {
var image = $( this ),
src = element.attr( "data-src" );
if ( src ) {
promises.push(
$.loadImage( src ).then(function() {
image.attr( "src", src );
}, function() {
image.attr( "src", "error.png" );
})
);
}
});
promises.push( panel.slideDownPromise() );
$.when.apply( null, promises ).done(function() {
panel.fadeIn();
});
});

这里的诀窍是跟踪所有 $.loadImage() 承诺。稍后,我们使用 $.when() 将它们与面板 .slideDown() 动画结合起来。因此,当首次单击按钮时,面板将向下滑动,图像将开始加载。一旦面板完成向下滑动并且所有图像都已加载,则面板将淡入,并且仅在此时才会淡入。

link 在特定延迟后在页面上加载图像

为了在整个页面上实现延迟图像显示,可以使用 HTML 中的以下格式。

1
2
3
4
<img data-src="image1.png" data-after="1000" src="placeholder.png" alt="">
<img data-src="image2.png" data-after="1000" src="placeholder.png" alt="">
<img data-src="image1.png" src="placeholder.png" alt="">
<img data-src="image2.png" data-after="2000" src="placeholder.png" alt="">

它所说的非常直接

  • 加载image1.png并立即显示第三张图片,第一张图片在 1 秒后显示
  • 加载image2.png并显示第二张图片在 1 秒后显示,第四张图片在 2 秒后显示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$( "img" ).each(function() {
var element = $( this ),
src = element.attr( "data-src" ),
after = element.attr( "data-after" );
if ( src ) {
$.when(
$.loadImage( src ),
$.afterDOMReady( after )
).then(function() {
element.attr( "src", src );
}, function() {
element.attr( "src", "error.png" );
}).done(function() {
element.fadeIn();
});
}
});

为了延迟加载图像本身

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$( "img" ).each(function() {
var element = $( this ),
src = element.attr( "data-src" ),
after = element.attr( "data-after" );
if ( src ) {
$.afterDOMReady( after, function() {
$.loadImage( src ).then(function() {
element.attr( "src", src );
}, function() {
element.attr( "src", "error.png" );
}).done(function() {
element.fadeIn();
});
});
}
});

此处,在延迟满足后加载图像。当您希望限制页面加载时的网络请求数量或网络请求时,这可能非常有意义。