@@ -123,6 +123,14 @@ mysteriously terminate subscriptions for a variety of reasons, meaning that any
123
123
code using subscriptions had to be wrapped in ugly complexity to be
124
124
usable in production.
125
125
126
+ INTEREST AWARENESS: In Conat there is a cluster-aware event driving way to
127
+ wait for interest in a subject. This is an extremely useful extension to
128
+ NATS functionality, since it makes it much easier to dynamically setup
129
+ a client and a server and exchange messages without having to poll and fail
130
+ potentially a few times. This makes certain operations involving complicated
131
+ steps behind the scenes -- upload a file, open a file to edit with sync, etc. --
132
+ feel more responsive.
133
+
126
134
USAGE:
127
135
128
136
The following should mostly work to interactively play around with this
@@ -1172,7 +1180,7 @@ export class Client extends EventEmitter {
1172
1180
publish = async (
1173
1181
subject : string ,
1174
1182
mesg ,
1175
- opts ? : PublishOptions ,
1183
+ opts : PublishOptions = { } ,
1176
1184
) : Promise < {
1177
1185
// bytes encoded (doesn't count some extra wrapping)
1178
1186
bytes : number ;
@@ -1187,12 +1195,45 @@ export class Client extends EventEmitter {
1187
1195
return { bytes : 0 , count : 0 } ;
1188
1196
}
1189
1197
await this . waitUntilSignedIn ( ) ;
1198
+ const start = Date . now ( ) ;
1190
1199
const { bytes, getCount, promise } = this . _publish ( subject , mesg , {
1191
1200
...opts ,
1192
1201
confirm : true ,
1193
1202
} ) ;
1194
1203
await promise ;
1195
- return { bytes, count : getCount ?.( ) ! } ;
1204
+ let count = getCount ?.( ) ! ;
1205
+
1206
+ if (
1207
+ opts . waitForInterest &&
1208
+ count != null &&
1209
+ count == 0 &&
1210
+ ! this . isClosed ( ) &&
1211
+ ( opts . timeout == null || Date . now ( ) - start <= opts . timeout )
1212
+ ) {
1213
+ await this . waitForInterest ( subject , {
1214
+ timeout : opts . timeout ? opts . timeout - ( Date . now ( ) - start ) : undefined ,
1215
+ } ) ;
1216
+ if ( this . isClosed ( ) ) {
1217
+ return { bytes, count } ;
1218
+ }
1219
+ const elapsed = Date . now ( ) - start ;
1220
+ const timeout = opts . timeout == null ? undefined : opts . timeout - elapsed ;
1221
+ // client and there is interest
1222
+ if ( timeout && timeout <= 500 ) {
1223
+ // but... not enough time left to try again even if there is interest,
1224
+ // i.e., will fail anyways due to network latency
1225
+ return { bytes, count } ;
1226
+ }
1227
+ const { getCount, promise } = this . _publish ( subject , mesg , {
1228
+ ...opts ,
1229
+ timeout,
1230
+ confirm : true ,
1231
+ } ) ;
1232
+ await promise ;
1233
+ count = getCount ?.( ) ! ;
1234
+ }
1235
+
1236
+ return { bytes, count } ;
1196
1237
} ;
1197
1238
1198
1239
private _publish = (
@@ -1316,19 +1357,11 @@ export class Client extends EventEmitter {
1316
1357
request = async (
1317
1358
subject : string ,
1318
1359
mesg : any ,
1319
- {
1320
- timeout = DEFAULT_REQUEST_TIMEOUT ,
1321
- // waitForInterest -- if publish fails due to no receivers and
1322
- // waitForInterest is true, will wait until there is a receiver
1323
- // and publish again:
1324
- waitForInterest = false ,
1325
- ...options
1326
- } : PublishOptions & { timeout ?: number ; waitForInterest ?: boolean } = { } ,
1360
+ { timeout = DEFAULT_REQUEST_TIMEOUT , ...options } : PublishOptions = { } ,
1327
1361
) : Promise < Message > => {
1328
1362
if ( timeout <= 0 ) {
1329
1363
throw Error ( "timeout must be positive" ) ;
1330
1364
}
1331
- const start = Date . now ( ) ;
1332
1365
const inbox = await this . getInbox ( ) ;
1333
1366
const inboxSubject = this . temporaryInboxSubject ( ) ;
1334
1367
const sub = new EventIterator < Message > ( inbox , inboxSubject , {
@@ -1343,39 +1376,13 @@ export class Client extends EventEmitter {
1343
1376
headers : { ...options ?. headers , [ REPLY_HEADER ] : inboxSubject } ,
1344
1377
} ;
1345
1378
const { count } = await this . publish ( subject , mesg , opts ) ;
1346
-
1347
1379
if ( ! count ) {
1348
- const giveUp = ( ) => {
1349
- sub . stop ( ) ;
1350
- throw new ConatError (
1351
- `request -- no subscribers matching '${ subject } '` ,
1352
- {
1353
- code : 503 ,
1354
- } ,
1355
- ) ;
1356
- } ;
1357
- if ( waitForInterest ) {
1358
- await this . waitForInterest ( subject , { timeout } ) ;
1359
- if ( this . state == "closed" ) {
1360
- throw Error ( "closed" ) ;
1361
- }
1362
- const remaining = timeout - ( Date . now ( ) - start ) ;
1363
- if ( remaining <= 1000 ) {
1364
- throw new ConatError ( "timeout" , { code : 408 } ) ;
1365
- }
1366
- // no error so there is very likely now interest, so we publish again:
1367
- const { count } = await this . publish ( subject , mesg , {
1368
- ...opts ,
1369
- timeout : remaining ,
1370
- } ) ;
1371
- if ( ! count ) {
1372
- giveUp ( ) ;
1373
- }
1374
- } else {
1375
- giveUp ( ) ;
1376
- }
1380
+ sub . stop ( ) ;
1381
+ // if you hit this, consider using the option waitForInterest:true
1382
+ throw new ConatError ( `request -- no subscribers matching '${ subject } '` , {
1383
+ code : 503 ,
1384
+ } ) ;
1377
1385
}
1378
-
1379
1386
for await ( const resp of sub ) {
1380
1387
sub . stop ( ) ;
1381
1388
return resp ;
@@ -1588,8 +1595,17 @@ interface PublishOptions {
1588
1595
// encoded message (using encoding) and any mesg parameter
1589
1596
// is *IGNORED*.
1590
1597
raw ?;
1598
+
1591
1599
// timeout used when publishing a message and awaiting a response.
1592
1600
timeout ?: number ;
1601
+
1602
+ // waitForInterest -- if publishing async so its possible to tell whether or not
1603
+ // there were any recipients, and there were NO recipients, it will wait until
1604
+ // there is a recipient and send again. This does NOT use polling, but instead
1605
+ // uses a cluster aware and fully event based primitive in the server.
1606
+ // There is thus only a speed penality doing this on failure and never
1607
+ // on success.
1608
+ waitForInterest ?: boolean ;
1593
1609
}
1594
1610
1595
1611
interface RequestManyOptions extends PublishOptions {
0 commit comments