@@ -5,6 +5,10 @@ import { exponentialBackoff } from "./backoff.ts";
55import { ErrorReplyError , isRetriableError } from "./errors.ts" ;
66import { BufReader } from "./vendor/https/deno.land/std/io/buf_reader.ts" ;
77import { BufWriter } from "./vendor/https/deno.land/std/io/buf_writer.ts" ;
8+ import {
9+ Deferred ,
10+ deferred ,
11+ } from "./vendor/https/deno.land/std/async/deferred.ts" ;
812import { delay } from "./vendor/https/deno.land/std/async/delay.ts" ;
913type Closer = Deno . Closer ;
1014
@@ -50,6 +54,12 @@ export class RedisConnection implements Connection {
5054 private _isConnected = false ;
5155 private backoff : Backoff ;
5256
57+ private commandQueue : {
58+ name : string ;
59+ args : RedisValue [ ] ;
60+ promise : Deferred < RedisReply > ;
61+ } [ ] = [ ] ;
62+
5363 get isClosed ( ) : boolean {
5464 return this . _isClosed ;
5565 }
@@ -104,48 +114,20 @@ export class RedisConnection implements Connection {
104114 await this . sendCommand ( "SELECT" , [ db ] ) ;
105115 }
106116
107- async sendCommand (
117+ sendCommand (
108118 command : string ,
109119 args ?: Array < RedisValue > ,
110120 ) : Promise < RedisReply > {
111- try {
112- const reply = await sendCommand (
113- this . writer ,
114- this . reader ,
115- command ,
116- args ?? kEmptyRedisArgs ,
117- ) ;
118- return reply ;
119- } catch ( error ) {
120- if (
121- ! isRetriableError ( error ) ||
122- this . isManuallyClosedByUser ( )
123- ) {
124- throw error ;
125- }
126-
127- for ( let i = 0 ; i < this . maxRetryCount ; i ++ ) {
128- // Try to reconnect to the server and retry the command
129- this . close ( ) ;
130- try {
131- await this . connect ( ) ;
132-
133- const reply = await sendCommand (
134- this . writer ,
135- this . reader ,
136- command ,
137- args ?? kEmptyRedisArgs ,
138- ) ;
139-
140- return reply ;
141- } catch { // TODO: use `AggregateError`?
142- const backoff = this . backoff ( i ) ;
143- await delay ( backoff ) ;
144- }
145- }
146-
147- throw error ;
121+ const promise = deferred < RedisReply > ( ) ;
122+ this . commandQueue . push ( {
123+ name : command ,
124+ args : args ?? kEmptyRedisArgs ,
125+ promise,
126+ } ) ;
127+ if ( this . commandQueue . length === 1 ) {
128+ this . processCommandQueue ( ) ;
148129 }
130+ return promise ;
149131 }
150132
151133 /**
@@ -221,6 +203,53 @@ export class RedisConnection implements Connection {
221203 }
222204 }
223205
206+ private async processCommandQueue ( ) {
207+ const [ command ] = this . commandQueue ;
208+ if ( ! command ) return ;
209+
210+ try {
211+ const reply = await sendCommand (
212+ this . writer ,
213+ this . reader ,
214+ command . name ,
215+ command . args ,
216+ ) ;
217+ command . promise . resolve ( reply ) ;
218+ } catch ( error ) {
219+ if (
220+ ! isRetriableError ( error ) ||
221+ this . isManuallyClosedByUser ( )
222+ ) {
223+ return command . promise . reject ( error ) ;
224+ }
225+
226+ for ( let i = 0 ; i < this . maxRetryCount ; i ++ ) {
227+ // Try to reconnect to the server and retry the command
228+ this . close ( ) ;
229+ try {
230+ await this . connect ( ) ;
231+
232+ const reply = await sendCommand (
233+ this . writer ,
234+ this . reader ,
235+ command . name ,
236+ command . args ,
237+ ) ;
238+
239+ return command . promise . resolve ( reply ) ;
240+ } catch { // TODO: use `AggregateError`?
241+ const backoff = this . backoff ( i ) ;
242+ await delay ( backoff ) ;
243+ }
244+ }
245+
246+ command . promise . reject ( error ) ;
247+ } finally {
248+ this . commandQueue . shift ( ) ;
249+ this . processCommandQueue ( ) ;
250+ }
251+ }
252+
224253 private isManuallyClosedByUser ( ) : boolean {
225254 return this . _isClosed && ! this . _isConnected ;
226255 }
0 commit comments