依次執行多項非同步任務
有時候,我們希望批量執行一組非同步任務,但是不是並行,而是依次執行,這組任務是動態的,在一個陣列裡,當然我們可以用 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){ ... } } |
以上就是今天的所有內容。ES 的新特性為我們提供了非常強大的功能,讓我們能夠更加優雅地寫程式碼。有任何問題,歡迎留言討論。