ES6(ES2015)のPromiseで配列の要素を、

  • 順番に処理したい(=直列または逐次処理)
  • 同時に処理したり(=並列)
  • 数個ずつ同時に処理したい(=直列の中で並列処理)

といった、3つのパターンで処理を書きたいなと思いました。

この時、参考にさせて頂いたのがQiitaの記事です。

Qiita
Promiseを複数組み合わせる時の基本パターン(直列、並列、分岐)

今回は上記の記事を参考にしながら、配列要素を処理するケースを考えてみたいと思います。

 develop or drink ?  photo by tea ©
Ads

配列要素を直列処理(逐次処理)

まず初めに、配列要素を順番に取り出して、それぞれの要素を処理していくケースです。

下記のサンプルコードはSAMPLE_TASKfileNameに含まれる画像ファイルを処理するjavascriptの例です。処理時間は例として、workに指定しているミリ秒数だけ掛かるとします。

直列処理には、reduce()を使って、配列要素を順番に処理していきます。

sample.js
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
'use strict'

// babelまたは、node v4.3以降で動作します。
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) => {
// console.log("complete!")
// console.log(inObj)
resolve(inObj)
})
})

実行すると、1.jpgから6.jpgまで順番に処理されていきます。Babelで結果を確認することも出来ますのでご覧ください。

bash
1
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.js
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
'use strict'

// babelまたは、node v4.3以降で動作します。
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) => {
// console.log("complete!")
// console.log(inObj)
resolve(inObj)
})
})

実行すると、1.jpgから6.jpgが同時に処理され、workが短い順番に、処理が完了します。Babelで結果を確認することも出来ますのでご覧ください。

bash
1
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.js
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
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'

// babelまたは、node v4.3以降で動作します。
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 }
]

// 配列を引数numに応じて、2次元配列に分割する関数
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)
}
})

// 2次元配列を1次元配列に変換する関数
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 )
}
}
// console.log("\n")
// console.log("最後に、2次元配列を1次元配列に変換する")
// console.log(retArr)
// console.log("\n")
resolve(retArr)
}
})



// 直列 in 並列パターン
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) => {
// console.log("complete!")
// console.log(inObj)
resolve(inObj)
})
})

実行すると、以下のように処理が進みます。

  1. parallelize()によって、1.jpgから3.jpg4.jpgから6.jpgの2次元配列要素に分割されます
  2. 1.jpgから3.jpgが並列処理され、3つ全ての処理が完了するまで待機します
  3. 次に4.jpgから6.jpgが並列処理され、3つ全ての処理が完了するまで待機します
  4. 最後にserialize()によって、分割された2次元配列が1次元配列に戻ります。

実行すると以下のような結果になります。Babelで結果を確認することも出来ますのでご覧ください。

bash
1
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 }