ECMAScript 6 的 Promise 是一个非常重要的特性,有了它,JavaScript 异步嵌套的问题算是得到了比较好的解决。同时,Promise 也是 ES7 中 async/await 的基础。
介绍 Promise 基础的文章已经非常多了,在这里就不再讲解 Promise 本身的用法。本文主要介绍利用 Promise 的特性改良异步 Timer 的一种思路。
在产品中,异步加载资源的时候,有时会有一种业务需求,需要执行某个任务,直到任务返回 false 或者超过某个时间限制。
例如,在 360网址导航 中,存在类似这样的代码:
1 2 3 4 5 6 7 8 9 10 11 12 |
qboot.await(function() { return hao360.g("channelloading-" + channel); }, function() { setTimeout(function() { if (hao360.g("channelview-" + channel).innerHTML.replace(/^[sxa0u3000]+|[u3000xa0s]+$/g, "") == "") { hao360.g("channelloading-" + channel).style.display = "block"; } }, 1000); }, null, 100, 100); |
这里的 qboot.await 实际上是一个异步的 timer,由于导航网页上的部分内容是异步加载的,因此用 qboot.await 来确保操作相关 DOM 的时候内容已经加载完毕。
qboot 的具体代码在 github 上可以找到。这里截取其中的片段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
/** * 轮询执行某个task,直到task返回false或者超过轮询最大次数上限 * 如果成功超过轮询上限,执行complete,否则执行abort * @param task 轮询的任务 * @param step 轮询间隔,以毫秒为单位 * @param max 最大轮询次数 * @param complete 超过最大次数,轮询成功 * @param abort task返回false,轮询被中断 */ poll : function(task, step, max, complete, abort){ step = step || 100; if(max == null) max = Infinity; if(max 0){ complete & complete(); return; } if(task() !== false){ setTimeout(function(){ qboot.poll(task, step, max-1, complete, abort); }, step); }else{ abort & abort(); } }, /** * 等待直到cond条件为true执行success * 如果等待次数超过max,则执行failer * @param cond await条件,返回true则执行success,否则继续等待,直到超过等待次数max,执行failer * @param success await成功 * @param failer await失败 * @param step 时间间隔 * @param max 最大次数 */ await : function(cond, success, failer, step, max){ qboot.poll(function(){ if(cond()){ success(); return false; } return true; }, step, max, failer); }, |
await 方法虽好,但是不够语义化,在这个支持 Promise 的时代,实现这个需求,我们可以有更好的设计思路,产生更加 语义化 的 API:
1 2 3 4 5 6 7 |
let promise = wait.every(100).before(10000).until(function(){ return hao360.g("channelloading-" + channel) || false; }); promise.then(function(channel){ //do sth. }); |
wait-promise 库
我们可以基于 Promise 实现一套优雅的 API,把它封装成 wait-promise 库。
根据常见应用场景设计如下 API:
check
wait.check(condition)
check
总是返回一个 promise,如果 condition 抛出未处理的异常或者返回一个 false 值,那么 promise 的状态为 reject,否则 resolve 该 promise。
例如:
1 2 3 4 5 6 7 |
let i = 1; let promise = wait.check(function(){ return i 1; }); promise.catch(function(err){ console.log(err.message); //will be check failed }); |
until
wait.until(condition)
until
每隔若干时间间隔(默认为100ms),检查一次 condition,直到当前 condition 不抛异常也不返回 false 值为止,满足条件后将 promise 的状态置为 resolve。
例如:
1 2 3 4 5 6 7 |
let i = 0; let promise = wait.until(function(){ return ++i >= 10; }); promise.then(function(){ console.log(i); //i will be 10 }); |
ES7 的用法:
1 2 3 4 5 6 7 8 |
let until = wait.until; async function foo(){ let i = 0; await until(() => ++i > 10); bar(); } |
till
wait.till(condition)
与 until
类似,不同之处在于 condition 返回 true 的时候 till
将 promise 的状态置为 resolve。同样与 until
相反,如果 till
的 condition 抛异常, promise 的状态为 reject 。
例如(通常情形下和 until 用法一样):
1 2 3 4 5 6 7 |
let i = 0; let promise = wait.till(function(){ return ++i >= 10; }); promise.then(function(){ console.log(i); //i will be 10 }); |
before
wait.before(millisec).until(condition)
before
通常和 until
组合使用,给 until 轮询限制一个最长时间,超过时间之后,如果 condition 还是抛异常或者返回 false,则将 promise 的状态置成 reject。
例如:
1 2 3 4 5 6 7 |
let i = 0; let promise = wait.before(200).until(function(){ return ++i >= 10; }); promise.catch(function(err){ console.log(i, err.message); //2 check failed }); |
通常我们可以用来判断资源是否加载成功(包含超时):
1 2 3 4 5 6 7 8 9 |
var promise = wait.before(5000).until(function(){ |