有时候,我们希望批量执行一组异步任务,但是不是并行,而是依次执行,这组任务是动态的,在一个数组里,当然我们可以用 for 循环然后一个一个 await 执行,但是还有另外一种方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
async function taskReducer(promise, action){ let res = await promise; return action(res); } function sleep(ms){ return new Promise(resolve => setTimeout(resolve, ms)); } async function asyncTask(i){ await sleep(500); console.log(`task ${i} done`); return ++i; } [asyncTask, asyncTask, asyncTask].reduce(taskReducer, 0); |
在上面的例子里,我们定义了一个 taskReducer:
1 2 3 4 |
async function taskReducer(promise, action){ let res = await promise; return action(res); } |
这个 reducer 的两个参数是 promise 和 action,promise 是代表当前任务的 promise,而 action 是下一个要执行的任务。我们可以 await 当前 promise 执行当前任务,然后将执行结果传给下一个 action 就可以了。
这样我们可以调用:
1 |
[task1, task2, task3, ...].reduce(taskReducer, init); |
不管这些任务是同步还是异步都可以被依次执行。需要注意的是,每一个任务的返回值将是下一个任务的输入 promise 或者 value。
generator 与 async/await 一同使用
将上面的代码进一步扩展,我们发现,它可以支持 generator 与 async/await 一同使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
async function reducer(promise, action){ let res = await promise; return action(res); } function tick(i){ console.log(i); return new Promise(resolve => setTimeout(()=>resolve(++i), 1000)); } function continuous(...functors){ return async function(input){ return await functors.reduce(reducer, input) } } function * timing(count = 5){ for(let i = 0; i yield tick; } } continuous(...timing(10))(0); |
在上面的例子里,我们定义了一个计时 tick 函数,我们通过 timing 来连续调用它,而 timing 是一个 generator,计时器显然是异步函数,然而我们可以:
1 |
continuous(...timing(10))(0); |
而这里的 continuous 其实就是前面的 reduce 的封装。
使用 Proxy 实现 PHP 中的常用“魔术方法”
PHP 中有 __get 、 __set 和 __call 三个强大的魔术方法,可以实现对不存在的属性的读写和方法调用。在新的 ES 标准中添加了 Proxy 类,它可以构造 Proxy 对象,用来“重载”对象的属性和方法读写,从而实现类似于 PHP 的魔术方法:
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 44 45 46 47 48 49 50 51 52 53 |
function Magical(Class){ return function(...args){ return new Proxy(new Class(...args), { get: function(target, key, receiver) { if(typeof target[key] !== ‘undefined‘){ return target[key]; } if (typeof target.__get === ‘function‘){ return target.__get(key); } else if (typeof target.__call === ‘function‘) { return function(...args){ target.__call.apply(this, [key, ...args]); }; } }, set: function(target, key, value, receiver){ if(typeof target[key] !== ‘undefined‘){ target[key] = value; return; } if (typeof target.__set === ‘function‘){ target.__set(key, value); return; } } }); } } class Foo{ __set(key, value){ this[key] = value * 2; } __get(key){ return this.b; } __call(key, ...args){ console.log(`call method ${key} with ${args}`); } b(...args){ console.log(`b exists: ${args}`); } } Foo = Magical(Foo); var f = new Foo(); f.b(1,2,3); f.a(4,5,6); f.c = 3; console.log(f.c); |
上面的例子里,我们在对象构造的时候,分别“代理”对象实例的属性 get 和 set 方法,如果对象上已存在某个属性或方法,代理直接返回或操作该属性。否则,判断对象上是否有 __get、__set 或者 __call 方法,有的话,做相应的处理。
这里我们使用装饰器模式,定义了一个 Magical 装饰器函数,让它来处理希望使用 Magical 的类。
等到 ES Decorators 标准化了之后,我们就可以使用更加优雅的写法了:
1 2 3 4 5 6 |
@magical class Foo { __call(key, ...args){ ... } } 有时候,我们希望批量执行一组异步任务,但是不是并行,而是依次执行,这组任务是动态的,在一个数组里,当然我们可以用 for 循环然后一个一个 await 执行,但是还有另外一种方式:
在上面的例子里,我们定义了一个 taskReducer:
这个 reducer 的两个参数是 promise 和 action,promise 是代表当前任务的 promise,而 action 是下一个要执行的任务。我们可以 await 当前 promise 执行当前任务,然后将执行结果传给下一个 action 就可以了。 这样我们可以调用:
不管这些任务是同步还是异步都可以被依次执行。需要注意的是,每一个任务的返回值将是下一个任务的输入 promise 或者 value。 generator 与 async/await 一同使用将上面的代码进一步扩展,我们发现,它可以支持 generator 与 async/await 一同使用:
在上面的例子里,我们定义了一个计时 tick 函数,我们通过 timing 来连续调用它,而 timing 是一个 generator,计时器显然是异步函数,然而我们可以:
而这里的 continuous 其实就是前面的 reduce 的封装。 使用 Proxy 实现 PHP 中的常用“魔术方法”PHP 中有 __get 、 __set 和 __call 三个强大的魔术方法,可以实现对不存在的属性的读写和方法调用。在新的 ES 标准中添加了 Proxy 类,它可以构造 Proxy 对象,用来“重载”对象的属性和方法读写,从而实现类似于 PHP 的魔术方法:
上面的例子里,我们在对象构造的时候,分别“代理”对象实例的属性 get 和 set 方法,如果对象上已存在某个属性或方法,代理直接返回或操作该属性。否则,判断对象上是否有 __get、__set 或者 __call 方法,有的话,做相应的处理。 这里我们使用装饰器模式,定义了一个 Magical 装饰器函数,让它来处理希望使用 Magical 的类。 等到 ES Decorators 标准化了之后,我们就可以使用更加优雅的写法了:
本文源于 www.71key.com |