ES6(ES2015)のPromiseで配列の要素を、
- 順番に処理したい(=直列または逐次処理)
- 同時に処理したり(=並列)
- 数個ずつ同時に処理したい(=直列の中で並列処理)
といった、3つのパターンで処理を書きたいなと思いました。
この時、参考にさせて頂いたのがQiitaの記事です。
Qiita
Promiseを複数組み合わせる時の基本パターン(直列、並列、分岐)
今回は上記の記事を参考にしながら、配列要素を処理するケースを考えてみたいと思います。
develop or drink ? photo by tea © 配列要素を直列処理(逐次処理)
まず初めに、配列要素を順番に取り出して、それぞれの要素を処理していくケースです。
下記のサンプルコードはSAMPLE_TASK
のfileName
に含まれる画像ファイルを処理するjavascriptの例です。処理時間は例として、work
に指定しているミリ秒数だけ掛かるとします。
直列処理には、reduce()
を使って、配列要素を順番に処理していきます。
sample.js1 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
| 'use strict'
const SAMPLE_TASK = [ { fileName: "1.jpg" , work: 6000 } , { fileName: "2.jpg" , work: 5000 } , { fileName: "3.jpg" , work: 4000 } , { fileName: "4.jpg" , work: 3000 } , { fileName: "5.jpg" , work: 2000 } , { fileName: "6.jpg" , work: 1000 } ]
let myImageResize = (inObj) => inObj.reduce( (promise, value) => promise.then( (editedArray) => parallel(value).then( (editedElement) => { editedArray.push(editedElement); return editedArray; }) ) , Promise.resolve([]))
let parallel = (fileObj) => new Promise( (resolve, reject) => { setTimeout( () => { console.log(fileObj) resolve(fileObj) } , fileObj.work ) })
myImageResize( SAMPLE_TASK ) .then( (inObj) => { return new Promise( (resolve , reject) => { resolve(inObj) }) })
|
実行すると、1.jpg
から6.jpg
まで順番に処理されていきます。Babelで結果を確認することも出来ますのでご覧ください。
bash1 2 3 4 5 6 7
| $ node sample.js { fileName: '1.jpg', work: 6000 } { fileName: '2.jpg', work: 5000 } { fileName: '3.jpg', work: 4000 } { fileName: '4.jpg', work: 3000 } { fileName: '5.jpg', work: 2000 } { fileName: '6.jpg', work: 1000 }
|
配列要素を並列処理
続いて、並列処理を書いていきたいと思います。並列処理にはPromise.all()
とmap()
を使って、取り出した配列要素を同時並行で処理していきます。
sample.js1 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
| 'use strict'
const SAMPLE_TASK = [ { fileName: "1.jpg" , work: 6000 } , { fileName: "2.jpg" , work: 5000 } , { fileName: "3.jpg" , work: 4000 } , { fileName: "4.jpg" , work: 3000 } , { fileName: "5.jpg" , work: 2000 } , { fileName: "6.jpg" , work: 1000 } ]
let myImageResize = (inObj) => parallel( inObj ) let parallel = (inObj) => Promise.all( inObj.map( (fileObj) => new Promise( ( resolve , reject ) => { setTimeout( () => { console.log(fileObj) resolve(fileObj) } , fileObj.work ) }) ))
myImageResize( SAMPLE_TASK ) .then( (inObj) => { return new Promise( (resolve , reject) => { resolve(inObj) }) })
|
実行すると、1.jpg
から6.jpg
が同時に処理され、work
が短い順番に、処理が完了します。Babelで結果を確認することも出来ますのでご覧ください。
bash1 2 3 4 5 6 7
| $ node sample.js { fileName: '6.jpg', work: 1000 } { fileName: '5.jpg', work: 2000 } { fileName: '4.jpg', work: 3000 } { fileName: '3.jpg', work: 4000 } { fileName: '2.jpg', work: 5000 } { fileName: '1.jpg', work: 6000 }
|
直列処理の中に並列処理を入れた、混合処理
最後に、直列処理と並列処理を組み合わせた処理を考えてみたいと思います。
今度は直列処理で使ったreduce()
と、並列処理で使ったPromise.all()
とmap()
を組み合わせます。
sample.js1 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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| 'use strict'
const SAMPLE_TASK = [ { fileName: "1.jpg" , work: 6000 } , { fileName: "2.jpg" , work: 5000 } , { fileName: "3.jpg" , work: 4000 } , { fileName: "4.jpg" , work: 3000 } , { fileName: "5.jpg" , work: 2000 } , { fileName: "6.jpg" , work: 1000 } ]
let parallelize = ( inArr , num ) => new Promise( ( resolve , reject ) => { let retArr = [] if( !inArr ){ reject( new Error( 'The array data is invalid.' ) ) }else{ for( let arr of inArr.entries() ){ if( arr[0] % num == 0 )retArr.push([]) retArr[ retArr.length -1 ].push( arr[1] ) } console.log("まず、配列を2次元配列に変換する") console.log(retArr) console.log("\n") resolve(retArr) } })
let serialize = ( inArr ) => new Promise( ( resolve , reject ) => { let retArr = [] if( !inArr ){ reject( new Error( 'The array data is invalid.' ) ) }else{ for( let arr2 of inArr ){ for( let arr1 of arr2){ retArr.push( arr1 ) } } resolve(retArr) } })
let myImageResize = (inObj) => inObj.reduce( (promise, value) => promise.then( (editedArray) => parallel(value).then( (editedElement) => { editedArray.push(editedElement); return editedArray; }) ) , Promise.resolve([]))
let parallel = (inObj) => Promise.all( inObj.map( (fileObj) => new Promise( ( resolve , reject ) => { setTimeout( () => { console.log(fileObj) resolve(fileObj) } , fileObj.work ) }) ))
parallelize( SAMPLE_TASK , 3) .then( myImageResize ) .then( serialize ) .then( (inObj) => { return new Promise( (resolve , reject) => { resolve(inObj) }) })
|
実行すると、以下のように処理が進みます。
parallelize()
によって、1.jpg
から3.jpg
、4.jpg
から6.jpg
の2次元配列要素に分割されます 1.jpg
から3.jpg
が並列処理され、3つ全ての処理が完了するまで待機します - 次に
4.jpg
から6.jpg
が並列処理され、3つ全ての処理が完了するまで待機します - 最後に
serialize()
によって、分割された2次元配列が1次元配列に戻ります。
実行すると以下のような結果になります。Babelで結果を確認することも出来ますのでご覧ください。
bash1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| $ node sample.js まず、配列を2次元配列に変換する [ [ { fileName: '1.jpg', work: 6000 }, { fileName: '2.jpg', work: 5000 }, { fileName: '3.jpg', work: 4000 } ], [ { fileName: '4.jpg', work: 3000 }, { fileName: '5.jpg', work: 2000 }, { fileName: '6.jpg', work: 1000 } ] ]
{ fileName: '3.jpg', work: 4000 } { fileName: '2.jpg', work: 5000 } { fileName: '1.jpg', work: 6000 } { fileName: '6.jpg', work: 1000 } { fileName: '5.jpg', work: 2000 } { fileName: '4.jpg', work: 3000 }
|