Skip to content

Commit cec9d0f

Browse files
committed
🎉 Initial commit
Signed-off-by: kei-g <km.8k6ce+github@gmail.com>
0 parents  commit cec9d0f

11 files changed

+526
-0
lines changed

.editorconfig

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# EditorConfig is awesome: https://EditorConfig.org
2+
3+
# top-most EditorConfig file
4+
root = true
5+
6+
[*]
7+
charset = utf-8
8+
end_of_line = lf
9+
indent_size = 2
10+
indent_style = space
11+
insert_final_newline = true
12+
tab_width = 2
13+
trim_trailing_whitespace = true
14+
15+
[*.md]
16+
max_line_length = off
17+
trim_trailing_whitespace = false

.eslintrc.json

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"env": {
3+
"browser": true,
4+
"es2021": true,
5+
"node": true
6+
},
7+
"extends": [
8+
"eslint:recommended",
9+
"plugin:@typescript-eslint/recommended"
10+
],
11+
"parser": "@typescript-eslint/parser",
12+
"parserOptions": {
13+
"ecmaVersion": 12,
14+
"sourceType": "module"
15+
},
16+
"plugins": [
17+
"@typescript-eslint"
18+
],
19+
"rules": {
20+
"indent": [
21+
"error",
22+
2,
23+
{
24+
"SwitchCase": 1
25+
}
26+
],
27+
"linebreak-style": [
28+
"error",
29+
"unix"
30+
],
31+
"quotes": [
32+
"error",
33+
"single"
34+
],
35+
"semi": [
36+
"error",
37+
"never"
38+
]
39+
}
40+
}

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
**/.nyc_output/
2+
**/.vscode/
3+
**/build/
4+
**/coverage/
5+
**/lib/
6+
**/node_modules/
7+
**/package-lock.json

.mocharc.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"exit": true,
3+
"extension": [
4+
"ts"
5+
],
6+
"require": "ts-node/register",
7+
"timeout": 5000
8+
}

.travis.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
cache: npm
2+
language: node_js
3+
node_js:
4+
- 14
5+
- 15
6+
- 16
7+
notifications:
8+
email: false
9+
script:
10+
- npm test

LICENSE

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
BSD 3-Clause License
2+
3+
Copyright (c) 2021-, kei-g
4+
All rights reserved.
5+
6+
Redistribution and use in source and binary forms, with or without
7+
modification, are permitted provided that the following conditions are met:
8+
9+
1. Redistributions of source code must retain the above copyright notice, this
10+
list of conditions and the following disclaimer.
11+
12+
2. Redistributions in binary form must reproduce the above copyright notice,
13+
this list of conditions and the following disclaimer in the documentation
14+
and/or other materials provided with the distribution.
15+
16+
3. Neither the name of the copyright holder nor the names of its
17+
contributors may be used to endorse or promote products derived from
18+
this software without specific prior written permission.
19+
20+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# async-iterable-queue [![License](https://img.shields.io/github/license/kei-g/async-iterable-queue)](https://opensource.org/licenses/BSD-3-Clause) [![Libraries.io dependency status for latest release](https://img.shields.io/librariesio/release/npm/async-iterable-queue)](https://npmjs.com/package/async-iterable-queue?activeTab=dependencies) [![Travis CI](https://img.shields.io/travis/com/kei-g/async-iterable-queue?logo=travis&style=flat)](https://www.travis-ci.com/github/kei-g/async-iterable-queue) [![npm](https://img.shields.io/npm/v/async-iterable-queue&style=flat)](https://npmjs.com/package/async-iterable-queue)
2+
3+
[![npms.io (maintenance)](https://img.shields.io/npms-io/maintenance-score/async-iterable-queue)](https://npms.io/search?q=async-iterable-queue) [![npms.io (quality)](https://img.shields.io/npms-io/quality-score/async-iterable-queue)](https://npms.io/search?q=async-iterable-queue)

async-iterable-queue.ts

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
import { EventEmitter } from 'stream'
2+
3+
/**
4+
* 非同期反復可能な先入れ先出し型の待ち行列への非同期反復子
5+
*/
6+
class AIQAsyncIterator<T> implements AsyncIterator<T> {
7+
/**
8+
* 事象発生器への参照
9+
*/
10+
readonly #emitter: EventEmitter
11+
12+
/**
13+
* 反復結果解決関数の配列への参照
14+
*/
15+
readonly #resolvers: IteratorResultResolver<T>[]
16+
17+
/**
18+
* コンストラクタ
19+
* @param emitter 事象発生器への参照
20+
* @param resolvers 反復結果解決関数の配列への参照
21+
*/
22+
constructor(emitter: EventEmitter, resolvers: IteratorResultResolver<T>[]) {
23+
this.#emitter = emitter
24+
this.#resolvers = resolvers
25+
}
26+
27+
/**
28+
* 次の要素を返す
29+
* @returns 次の要素
30+
*/
31+
next(): Promise<IteratorResult<T>> {
32+
return new Promise((resolve: IteratorResultResolver<T>) => (
33+
this.#resolvers.push(resolve),
34+
this.#emitter.emit('deq')
35+
))
36+
}
37+
}
38+
39+
/**
40+
* 非同期反復可能な先入れ先出し型の待ち行列の状態を表す型
41+
*/
42+
type AIQState = 'ending' | 'finished'
43+
44+
/**
45+
* 非同期反復可能な先入れ先出し型の待ち行列
46+
*/
47+
export class AsyncIterableQueue<T> implements AsyncIterable<T> {
48+
/**
49+
* 事象発生器
50+
*/
51+
readonly #emitter = new EventEmitter()
52+
53+
/**
54+
* 先入れ先出し型の待ち行列
55+
*/
56+
readonly #queue = [] as Terminatable<T>[]
57+
58+
/**
59+
* 反復結果解決関数の配列
60+
*/
61+
readonly #resolvers = [] as IteratorResultResolver<T>[]
62+
63+
/**
64+
* この待ち行列の現在の状態
65+
*/
66+
#state?: AIQState
67+
68+
/**
69+
* コンストラクタ
70+
*/
71+
constructor() {
72+
const resolveAsync = createAsyncResolver({
73+
finish: () => {
74+
const state = this.#state
75+
this.#state = 'finished'
76+
return state
77+
},
78+
resolvers: this.#resolvers,
79+
})
80+
this.#emitter.on('deq', async () => {
81+
while (this.#queue.length && this.#resolvers.length)
82+
await resolveAsync(this.#queue.shift())
83+
})
84+
this.#emitter.on('enq', async (value: Terminatable<T>) =>
85+
this.#resolvers.length ?
86+
await resolveAsync(value) :
87+
this.#queue.push(value)
88+
)
89+
}
90+
91+
/**
92+
* この待ち行列への要素の追加を終了する
93+
* @param cb 終端が読み取られた後に呼ばれるコールバック関数
94+
*/
95+
end(cb?: NoParameterCallback): Promise<void> {
96+
const state = this.#state
97+
if (state)
98+
throw new Error(state)
99+
this.#state = 'ending'
100+
return new Promise((resolve: Resolver<void>) => (
101+
this.#emitter.emit('enq', new Terminator(cb)),
102+
resolve()
103+
))
104+
}
105+
106+
/**
107+
* この待ち行列の末尾に要素を追加する
108+
* @param value 要素の値
109+
*/
110+
push(value: T): Promise<void> {
111+
const state = this.#state
112+
if (state)
113+
throw new Error(state)
114+
return new Promise((resolve: Resolver<void>) => (
115+
this.#emitter.emit('enq', value),
116+
resolve()
117+
))
118+
}
119+
120+
/**
121+
* 非同期反復子を返す
122+
* @returns 非同期反復子
123+
*/
124+
[Symbol.asyncIterator](): AsyncIterator<T> {
125+
return new AIQAsyncIterator(this.#emitter, this.#resolvers)
126+
}
127+
}
128+
129+
/**
130+
* 反復結果解決関数を非同期的に処理する関数を作成する際のパラメータの型
131+
*/
132+
type AsyncResolverCreateParameter<T> = {
133+
/**
134+
* 非同期反復可能な先入れ先出し型の待ち行列を終了状態にする
135+
* @returns 以前の状態を返す
136+
*/
137+
finish(): AIQState
138+
139+
/**
140+
* 反復結果解決関数の配列への参照を取得する
141+
*/
142+
get resolvers(): IteratorResultResolver<T>[]
143+
}
144+
145+
/**
146+
* 反復結果解決関数型
147+
*/
148+
type IteratorResultResolver<T> = Resolver<IteratorResult<T>>
149+
150+
/**
151+
* 引数無しコールバック関数型
152+
*/
153+
type NoParameterCallback = () => PromiseLike<void> | void
154+
155+
/**
156+
* 解決関数型
157+
*/
158+
type Resolver<T> = SingleParameterAction<T>
159+
160+
/**
161+
* 引数1個の関数型
162+
*/
163+
type SingleParameterAction<T> = (arg: T) => void
164+
165+
/**
166+
* 終端
167+
*/
168+
class Terminator {
169+
/**
170+
* コンストラクタ
171+
* @param cb コールバック関数
172+
*/
173+
constructor(private readonly cb?: NoParameterCallback) {
174+
}
175+
176+
/**
177+
* コールバック関数を呼び出す
178+
*/
179+
call(): Promise<void> {
180+
return new Promise((
181+
resolve: Resolver<void>,
182+
reject: SingleParameterAction<unknown>,
183+
) => {
184+
if (this.cb)
185+
try {
186+
const result = this.cb()
187+
if (result instanceof Promise)
188+
result.catch(reject).then(resolve)
189+
else
190+
resolve()
191+
}
192+
catch (err: unknown) {
193+
reject(err)
194+
}
195+
else
196+
resolve()
197+
})
198+
}
199+
}
200+
201+
/**
202+
* 終端可能型
203+
*/
204+
type Terminatable<T> = Terminator | T
205+
206+
/**
207+
* 反復結果解決関数を非同期的に処理する関数を作成する
208+
* @param param パラメータ
209+
* @returns 反復結果解決関数を非同期的に処理する関数を返す
210+
*/
211+
const createAsyncResolver = <T>(param: AsyncResolverCreateParameter<T>) => {
212+
const resolveAsync = (result: IteratorResult<T>) =>
213+
new Promise((callback: Resolver<void>) => {
214+
const resolver = param.resolvers.shift()
215+
resolver(result)
216+
callback()
217+
})
218+
return async (value: Terminatable<T>) => {
219+
if (value instanceof Terminator) {
220+
const state = param.finish()
221+
await resolveAsync({ done: true } as IteratorResult<T>)
222+
if (state === 'ending')
223+
await value.call()
224+
}
225+
else
226+
await resolveAsync({
227+
done: false,
228+
value,
229+
})
230+
}
231+
}

0 commit comments

Comments
 (0)