vrpc 3.15.0 → 3.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/browser/vrpc.js +1 -7
- package/package.json +1 -1
- package/vrpc/VrpcAdapter.js +19 -7
- package/vrpc/VrpcAgent.js +35 -5
- package/vrpc/VrpcBrowserAdapter.js +33 -8
- package/vrpc/VrpcBrowserAgent.js +35 -5
- package/vrpc/VrpcClient.js +82 -11
package/package.json
CHANGED
package/vrpc/VrpcAdapter.js
CHANGED
|
@@ -661,8 +661,15 @@ class VrpcAdapter {
|
|
|
661
661
|
const [instanceId, ...args] = json.a
|
|
662
662
|
VrpcAdapter._assertInstanceName(instanceId)
|
|
663
663
|
const existing = VrpcAdapter._instances.get(instanceId)
|
|
664
|
-
if (existing
|
|
665
|
-
|
|
664
|
+
if (existing) {
|
|
665
|
+
// an id is never re-homed: an isolated instance stays isolated
|
|
666
|
+
if (existing.isIsolated) {
|
|
667
|
+
throw new Error(`Instance '${instanceId}' exists as an isolated instance`)
|
|
668
|
+
}
|
|
669
|
+
// a create of a name that exists returns it unchanged: its class
|
|
670
|
+
// and arguments stay, and nothing announces it (no 'create' event)
|
|
671
|
+
json.r = instanceId
|
|
672
|
+
return existing.instance
|
|
666
673
|
}
|
|
667
674
|
instance = VrpcAdapter._create(className, instanceId, ...args)
|
|
668
675
|
VrpcAdapter._instances.set(instanceId, {
|
|
@@ -1177,13 +1184,15 @@ class VrpcAdapter {
|
|
|
1177
1184
|
}
|
|
1178
1185
|
|
|
1179
1186
|
/**
|
|
1180
|
-
* Answers a promise twice: at once with `r: '__p__<f>-<n>'`, and
|
|
1181
|
-
* settled with a second answer under that id, `{ s, i, r }` or
|
|
1182
|
-
* `{ s, i, e }`.
|
|
1187
|
+
* Answers a promise twice: at once with `r: '__p__<f>-<nonce>-<n>'`, and
|
|
1188
|
+
* once it settled with a second answer under that id, `{ s, i, r }` or
|
|
1189
|
+
* `{ s, i, e }`. A client waits for the second answer under the bare id,
|
|
1190
|
+
* whichever agent sends it: the nonce keeps the ids of two agents apart,
|
|
1191
|
+
* whose counters may well stand at the same value.
|
|
1183
1192
|
*/
|
|
1184
1193
|
static _handlePromise (json, promise) {
|
|
1185
1194
|
const cid = VrpcAdapter._correlationId++ % Number.MAX_SAFE_INTEGER
|
|
1186
|
-
const id = `__p__${json.f}-${cid}`
|
|
1195
|
+
const id = `__p__${json.f}-${VrpcAdapter._promiseNonce}-${cid}`
|
|
1187
1196
|
const { s } = json
|
|
1188
1197
|
json.r = id
|
|
1189
1198
|
promise.then(
|
|
@@ -1311,7 +1320,8 @@ class VrpcAdapter {
|
|
|
1311
1320
|
/**
|
|
1312
1321
|
* Event 'create'
|
|
1313
1322
|
*
|
|
1314
|
-
* Emitted
|
|
1323
|
+
* Emitted once an instance was created. A create of a name that exists
|
|
1324
|
+
* returns that instance and emits nothing.
|
|
1315
1325
|
*
|
|
1316
1326
|
* @event VrpcAdapter#create
|
|
1317
1327
|
* @type {Object}
|
|
@@ -1391,6 +1401,8 @@ VrpcAdapter._functionRegistry = new Map()
|
|
|
1391
1401
|
VrpcAdapter._instances = new Map()
|
|
1392
1402
|
VrpcAdapter._emitter = new EventEmitter()
|
|
1393
1403
|
VrpcAdapter._correlationId = 0
|
|
1404
|
+
// tells this process's promise ids from those of every other agent
|
|
1405
|
+
VrpcAdapter._promiseNonce = nanoid(8)
|
|
1394
1406
|
// the event registrations of every context and their subscriptions
|
|
1395
1407
|
VrpcAdapter._registrations = new Registrations({
|
|
1396
1408
|
publish: message => VrpcAdapter._callback(message),
|
package/vrpc/VrpcAgent.js
CHANGED
|
@@ -646,6 +646,9 @@ class VrpcAgent extends EventEmitter {
|
|
|
646
646
|
] of VrpcAdapter._instances.entries()) {
|
|
647
647
|
batches.push(this._methodsTopic(className, instanceId))
|
|
648
648
|
}
|
|
649
|
+
// what the broker retains of this agent's classes: an earlier run's
|
|
650
|
+
// class that is no longer offered is cleared (_sweepClassInfo)
|
|
651
|
+
batches.push(`${this._baseTopic}/+/__classInfoConcise__`)
|
|
649
652
|
let pending = batches.length
|
|
650
653
|
const announce = () => {
|
|
651
654
|
// a connection that ended meanwhile announces nothing: the next
|
|
@@ -659,10 +662,6 @@ class VrpcAgent extends EventEmitter {
|
|
|
659
662
|
this._serving = true
|
|
660
663
|
this.emit('connect')
|
|
661
664
|
}
|
|
662
|
-
if (pending === 0) {
|
|
663
|
-
announce()
|
|
664
|
-
return
|
|
665
|
-
}
|
|
666
665
|
for (const topic of batches) {
|
|
667
666
|
this._mqttSubscribe(topic, undefined, () => {
|
|
668
667
|
pending -= 1
|
|
@@ -743,7 +742,38 @@ class VrpcAgent extends EventEmitter {
|
|
|
743
742
|
return topics
|
|
744
743
|
}
|
|
745
744
|
|
|
746
|
-
|
|
745
|
+
/**
|
|
746
|
+
* A class info under this agent's own name. What the broker retained of
|
|
747
|
+
* a class this agent does not offer is a ghost of an earlier run - a
|
|
748
|
+
* class info only `end({ unregister: true })` would have cleared - and
|
|
749
|
+
* clients would list the class forever: it is cleared, in full and
|
|
750
|
+
* concise. Everything else on the topic - the agent's own announcements
|
|
751
|
+
* and clears, routed back live - passes without a word.
|
|
752
|
+
*
|
|
753
|
+
* @returns {Boolean} whether the message was a class info
|
|
754
|
+
* @private
|
|
755
|
+
*/
|
|
756
|
+
_sweepClassInfo (topic, data, packet) {
|
|
757
|
+
const prefix = `${this._baseTopic}/`
|
|
758
|
+
if (!topic.startsWith(prefix)) return false
|
|
759
|
+
const tokens = topic.slice(prefix.length).split('/')
|
|
760
|
+
if (tokens.length !== 2) return false
|
|
761
|
+
const [className, info] = tokens
|
|
762
|
+
if (info !== '__classInfoConcise__' && info !== '__classInfo__') {
|
|
763
|
+
return false
|
|
764
|
+
}
|
|
765
|
+
const retained = !!packet && packet.retain
|
|
766
|
+
if (!retained || data.length === 0) return true
|
|
767
|
+
if (this._getClasses().includes(className)) return true
|
|
768
|
+
this._log.info(`Clearing the class info of ${className}, no longer offered`)
|
|
769
|
+
for (const name of ['__classInfo__', '__classInfoConcise__']) {
|
|
770
|
+
this._mqttPublish(`${prefix}${className}/${name}`, null, { retain: true })
|
|
771
|
+
}
|
|
772
|
+
return true
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
_handleMessage (topic, data, packet) {
|
|
776
|
+
if (this._sweepClassInfo(topic, data, packet)) return
|
|
747
777
|
let json
|
|
748
778
|
try {
|
|
749
779
|
json = JSON.parse(data.toString())
|
|
@@ -69,7 +69,9 @@ class VrpcAdapter {
|
|
|
69
69
|
* @param {Boolean} [options.withNew=true] If true, class will be constructed
|
|
70
70
|
* using the `new` operator
|
|
71
71
|
* @param {Object} [options.schema=null] If provided is used to validate ctor
|
|
72
|
-
* parameters (only works if registered code reflects a single class)
|
|
72
|
+
* parameters (only works if registered code reflects a single class). The
|
|
73
|
+
* browser bundle (`browser/vrpc.js`) does not validate: it warns once and
|
|
74
|
+
* constructs with the arguments as given
|
|
73
75
|
*/
|
|
74
76
|
static registerClass (
|
|
75
77
|
Klass,
|
|
@@ -467,8 +469,15 @@ class VrpcAdapter {
|
|
|
467
469
|
const [instanceId, ...args] = json.a
|
|
468
470
|
VrpcAdapter._assertInstanceName(instanceId)
|
|
469
471
|
const existing = VrpcAdapter._instances.get(instanceId)
|
|
470
|
-
if (existing
|
|
471
|
-
|
|
472
|
+
if (existing) {
|
|
473
|
+
// an id is never re-homed: an isolated instance stays isolated
|
|
474
|
+
if (existing.isIsolated) {
|
|
475
|
+
throw new Error(`Instance '${instanceId}' exists as an isolated instance`)
|
|
476
|
+
}
|
|
477
|
+
// a create of a name that exists returns it unchanged: its class
|
|
478
|
+
// and arguments stay, and nothing announces it (no 'create' event)
|
|
479
|
+
json.r = instanceId
|
|
480
|
+
return existing.instance
|
|
472
481
|
}
|
|
473
482
|
instance = VrpcAdapter._create(className, instanceId, ...args)
|
|
474
483
|
VrpcAdapter._instances.set(instanceId, {
|
|
@@ -912,6 +921,17 @@ class VrpcAdapter {
|
|
|
912
921
|
}
|
|
913
922
|
|
|
914
923
|
static _validate (schema, params) {
|
|
924
|
+
// the browser bundle leaves ajv out - a fifth of its size, for agents in
|
|
925
|
+
// a page, which are rare: there a schema is not checked
|
|
926
|
+
if (typeof Ajv !== 'function') {
|
|
927
|
+
if (!VrpcAdapter._warnedNoSchemas) {
|
|
928
|
+
VrpcAdapter._warnedNoSchemas = true
|
|
929
|
+
console.warn(
|
|
930
|
+
'vrpc: schema validation is not available in the browser build, constructor arguments are not checked'
|
|
931
|
+
)
|
|
932
|
+
}
|
|
933
|
+
return
|
|
934
|
+
}
|
|
915
935
|
if (!VrpcAdapter._ajv) {
|
|
916
936
|
VrpcAdapter._ajv = new Ajv({ useDefaults: true })
|
|
917
937
|
}
|
|
@@ -983,13 +1003,15 @@ class VrpcAdapter {
|
|
|
983
1003
|
}
|
|
984
1004
|
|
|
985
1005
|
/**
|
|
986
|
-
* Answers a promise twice: at once with `r: '__p__<f>-<n>'`, and
|
|
987
|
-
* settled with a second answer under that id, `{ s, i, r }` or
|
|
988
|
-
* `{ s, i, e }`.
|
|
1006
|
+
* Answers a promise twice: at once with `r: '__p__<f>-<nonce>-<n>'`, and
|
|
1007
|
+
* once it settled with a second answer under that id, `{ s, i, r }` or
|
|
1008
|
+
* `{ s, i, e }`. A client waits for the second answer under the bare id,
|
|
1009
|
+
* whichever agent sends it: the nonce keeps the ids of two agents apart,
|
|
1010
|
+
* whose counters may well stand at the same value.
|
|
989
1011
|
*/
|
|
990
1012
|
static _handlePromise (json, promise) {
|
|
991
1013
|
const cid = VrpcAdapter._correlationId++ % Number.MAX_SAFE_INTEGER
|
|
992
|
-
const id = `__p__${json.f}-${cid}`
|
|
1014
|
+
const id = `__p__${json.f}-${VrpcAdapter._promiseNonce}-${cid}`
|
|
993
1015
|
const { s } = json
|
|
994
1016
|
json.r = id
|
|
995
1017
|
promise.then(
|
|
@@ -1117,7 +1139,8 @@ class VrpcAdapter {
|
|
|
1117
1139
|
/**
|
|
1118
1140
|
* Event 'create'
|
|
1119
1141
|
*
|
|
1120
|
-
* Emitted
|
|
1142
|
+
* Emitted once an instance was created. A create of a name that exists
|
|
1143
|
+
* returns that instance and emits nothing.
|
|
1121
1144
|
*
|
|
1122
1145
|
* @event VrpcAdapter#create
|
|
1123
1146
|
* @type {Object}
|
|
@@ -1157,6 +1180,8 @@ VrpcAdapter._functionRegistry = new Map()
|
|
|
1157
1180
|
VrpcAdapter._instances = new Map()
|
|
1158
1181
|
VrpcAdapter._emitter = new EventEmitter()
|
|
1159
1182
|
VrpcAdapter._correlationId = 0
|
|
1183
|
+
// tells this process's promise ids from those of every other agent
|
|
1184
|
+
VrpcAdapter._promiseNonce = nanoid(8)
|
|
1160
1185
|
// the event registrations of every context and their subscriptions
|
|
1161
1186
|
VrpcAdapter._registrations = new Registrations({
|
|
1162
1187
|
publish: message => VrpcAdapter._callback(message),
|
package/vrpc/VrpcBrowserAgent.js
CHANGED
|
@@ -478,6 +478,9 @@ class VrpcAgent extends EventEmitter {
|
|
|
478
478
|
] of VrpcAdapter._instances.entries()) {
|
|
479
479
|
batches.push(this._methodsTopic(className, instanceId))
|
|
480
480
|
}
|
|
481
|
+
// what the broker retains of this agent's classes: an earlier run's
|
|
482
|
+
// class that is no longer offered is cleared (_sweepClassInfo)
|
|
483
|
+
batches.push(`${this._baseTopic}/+/__classInfoConcise__`)
|
|
481
484
|
let pending = batches.length
|
|
482
485
|
const announce = () => {
|
|
483
486
|
// a connection that ended meanwhile announces nothing: the next
|
|
@@ -490,10 +493,6 @@ class VrpcAgent extends EventEmitter {
|
|
|
490
493
|
this._serving = true
|
|
491
494
|
this.emit('connect')
|
|
492
495
|
}
|
|
493
|
-
if (pending === 0) {
|
|
494
|
-
announce()
|
|
495
|
-
return
|
|
496
|
-
}
|
|
497
496
|
for (const topic of batches) {
|
|
498
497
|
this._mqttSubscribe(topic, undefined, () => {
|
|
499
498
|
pending -= 1
|
|
@@ -566,7 +565,38 @@ class VrpcAgent extends EventEmitter {
|
|
|
566
565
|
return topics
|
|
567
566
|
}
|
|
568
567
|
|
|
569
|
-
|
|
568
|
+
/**
|
|
569
|
+
* A class info under this agent's own name. What the broker retained of
|
|
570
|
+
* a class this agent does not offer is a ghost of an earlier run - a
|
|
571
|
+
* class info only `end({ unregister: true })` would have cleared - and
|
|
572
|
+
* clients would list the class forever: it is cleared, in full and
|
|
573
|
+
* concise. Everything else on the topic - the agent's own announcements
|
|
574
|
+
* and clears, routed back live - passes without a word.
|
|
575
|
+
*
|
|
576
|
+
* @returns {Boolean} whether the message was a class info
|
|
577
|
+
* @private
|
|
578
|
+
*/
|
|
579
|
+
_sweepClassInfo (topic, data, packet) {
|
|
580
|
+
const prefix = `${this._baseTopic}/`
|
|
581
|
+
if (!topic.startsWith(prefix)) return false
|
|
582
|
+
const tokens = topic.slice(prefix.length).split('/')
|
|
583
|
+
if (tokens.length !== 2) return false
|
|
584
|
+
const [className, info] = tokens
|
|
585
|
+
if (info !== '__classInfoConcise__' && info !== '__classInfo__') {
|
|
586
|
+
return false
|
|
587
|
+
}
|
|
588
|
+
const retained = !!packet && packet.retain
|
|
589
|
+
if (!retained || data.length === 0) return true
|
|
590
|
+
if (this._getClasses().includes(className)) return true
|
|
591
|
+
this._log.info(`Clearing the class info of ${className}, no longer offered`)
|
|
592
|
+
for (const name of ['__classInfo__', '__classInfoConcise__']) {
|
|
593
|
+
this._mqttPublish(`${prefix}${className}/${name}`, null, { retain: true })
|
|
594
|
+
}
|
|
595
|
+
return true
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
_handleMessage (topic, data, packet) {
|
|
599
|
+
if (this._sweepClassInfo(topic, data, packet)) return
|
|
570
600
|
let json
|
|
571
601
|
try {
|
|
572
602
|
json = JSON.parse(data.toString())
|
package/vrpc/VrpcClient.js
CHANGED
|
@@ -180,6 +180,9 @@ class VrpcClient extends EventEmitter {
|
|
|
180
180
|
this._pendingWithdrawals = new Map()
|
|
181
181
|
// the client's own inbox is subscribed (answers can arrive)
|
|
182
182
|
this._inboxReady = false
|
|
183
|
+
// the answers this client waits for ({ agent, second, lose, forget }),
|
|
184
|
+
// lost when the path an answer would travel breaks (see _loseAnswers)
|
|
185
|
+
this._waiting = new Set()
|
|
183
186
|
// labels callbacks carry towards the agent's list of registrations
|
|
184
187
|
this._subscriberLabels = new WeakMap()
|
|
185
188
|
if (log === 'console') {
|
|
@@ -323,6 +326,12 @@ class VrpcClient extends EventEmitter {
|
|
|
323
326
|
this.emit('error', err)
|
|
324
327
|
})
|
|
325
328
|
|
|
329
|
+
// a clean session keeps nothing for this connection while it is gone:
|
|
330
|
+
// an answer published meanwhile never arrives
|
|
331
|
+
this._client.on('close', () => {
|
|
332
|
+
this._loseAnswers(() => true, 'this client lost its connection')
|
|
333
|
+
})
|
|
334
|
+
|
|
326
335
|
this._client.stream.on('error', err => {
|
|
327
336
|
this.emit('error', err)
|
|
328
337
|
})
|
|
@@ -377,7 +386,7 @@ class VrpcClient extends EventEmitter {
|
|
|
377
386
|
}
|
|
378
387
|
})
|
|
379
388
|
|
|
380
|
-
this._client.on('message', (topic, message) => {
|
|
389
|
+
this._client.on('message', (topic, message, packet) => {
|
|
381
390
|
if (message.length === 0) return
|
|
382
391
|
try {
|
|
383
392
|
const tokens = topic.split('/')
|
|
@@ -398,8 +407,21 @@ class VrpcClient extends EventEmitter {
|
|
|
398
407
|
// the agent's protocol version (undefined before 3)
|
|
399
408
|
this._agents[agent].v = v
|
|
400
409
|
if (status === 'offline') {
|
|
410
|
+
this._loseAnswers(
|
|
411
|
+
w => w.second && w.agent === agent,
|
|
412
|
+
'the agent went offline'
|
|
413
|
+
)
|
|
401
414
|
this._loseAgent(agent, v)
|
|
402
415
|
} else {
|
|
416
|
+
// an agent announces itself from a new connection only (a
|
|
417
|
+
// retained announcement is old news a subscription brings):
|
|
418
|
+
// the promises its former connection was to answer are gone
|
|
419
|
+
if (!packet.retain) {
|
|
420
|
+
this._loseAnswers(
|
|
421
|
+
w => w.second && w.agent === agent,
|
|
422
|
+
'the agent connected anew'
|
|
423
|
+
)
|
|
424
|
+
}
|
|
403
425
|
this._redeclareTo(agent)
|
|
404
426
|
this._flushWithdrawals(agent)
|
|
405
427
|
}
|
|
@@ -1400,6 +1422,7 @@ class VrpcClient extends EventEmitter {
|
|
|
1400
1422
|
v: VRPC_PROTOCOL_VERSION
|
|
1401
1423
|
})
|
|
1402
1424
|
)
|
|
1425
|
+
this._loseAnswers(() => true, 'the client ended')
|
|
1403
1426
|
this._eventEmitter.removeAllListeners()
|
|
1404
1427
|
this.removeAllListeners()
|
|
1405
1428
|
// the agents withdraw what this connection held: nothing to declare
|
|
@@ -1604,12 +1627,16 @@ class VrpcClient extends EventEmitter {
|
|
|
1604
1627
|
await this._waitUntilClassIsOnline(agent, className)
|
|
1605
1628
|
this._mqttPublish(topic, JSON.stringify(json))
|
|
1606
1629
|
return new Promise((resolve, reject) => {
|
|
1607
|
-
const
|
|
1630
|
+
const call = `Proxy creation for class "${className}" on agent "${agent}" and domain "${this._domain}"`
|
|
1608
1631
|
const timer = setTimeout(() => {
|
|
1609
|
-
|
|
1610
|
-
reject(new Error(
|
|
1632
|
+
forget()
|
|
1633
|
+
reject(new Error(`${call} timed out (> ${this._timeout} ms)`))
|
|
1611
1634
|
}, this._timeout)
|
|
1612
|
-
|
|
1635
|
+
const lose = reason => {
|
|
1636
|
+
clearTimeout(timer)
|
|
1637
|
+
reject(new Error(`${call} lost its answer: ${reason}`))
|
|
1638
|
+
}
|
|
1639
|
+
const forget = this._awaitAnswer(i, { agent, lose }, ({ e, r }) => {
|
|
1613
1640
|
clearTimeout(timer)
|
|
1614
1641
|
if (e) {
|
|
1615
1642
|
// the agent answers a throwing constructor with { message, cause }
|
|
@@ -1813,27 +1840,33 @@ class VrpcClient extends EventEmitter {
|
|
|
1813
1840
|
|
|
1814
1841
|
async _handleAgentAnswer ({ i, c, f, a }, agent) {
|
|
1815
1842
|
return new Promise((resolve, reject) => {
|
|
1816
|
-
const
|
|
1843
|
+
const call = `Function call "${c}::${f}()" on agent "${agent}"`
|
|
1817
1844
|
const timer = setTimeout(() => {
|
|
1818
|
-
|
|
1819
|
-
reject(new Error(
|
|
1845
|
+
forget()
|
|
1846
|
+
reject(new Error(`${call} timed out (> ${this._timeout} ms)`))
|
|
1820
1847
|
}, this._timeout)
|
|
1848
|
+
const lose = reason => {
|
|
1849
|
+
clearTimeout(timer)
|
|
1850
|
+
reject(new Error(`${call} lost its answer: ${reason}`))
|
|
1851
|
+
}
|
|
1821
1852
|
// a protocol-4 agent that answers a declaring call without an error
|
|
1822
1853
|
// holds every subscription the call carried
|
|
1823
1854
|
const confirm = () => {
|
|
1824
1855
|
const info = this._agents[agent]
|
|
1825
1856
|
if (info && info.v >= 4) this._confirmSubscriptions(a)
|
|
1826
1857
|
}
|
|
1827
|
-
this.
|
|
1858
|
+
const forget = this._awaitAnswer(i, { agent, lose }, data => {
|
|
1828
1859
|
clearTimeout(timer)
|
|
1829
1860
|
if (data.e) {
|
|
1830
1861
|
const { message, cause } = VrpcClient._prepareError(data.e)
|
|
1831
1862
|
reject(new Error(`[vrpc ${agent}-${c}-${f}]: ${message}`, { cause }))
|
|
1832
1863
|
} else {
|
|
1833
1864
|
const ret = data.r
|
|
1834
|
-
// Handle functions returning a promise
|
|
1865
|
+
// Handle functions returning a promise: the result follows when
|
|
1866
|
+
// it settles, however long that takes - unless the path it would
|
|
1867
|
+
// travel breaks first
|
|
1835
1868
|
if (typeof ret === 'string' && ret.substring(0, 5) === '__p__') {
|
|
1836
|
-
this.
|
|
1869
|
+
this._awaitAnswer(ret, { agent, lose, second: true }, promiseData => {
|
|
1837
1870
|
if (promiseData.e) {
|
|
1838
1871
|
const { message, cause } = VrpcClient._prepareError(
|
|
1839
1872
|
promiseData.e
|
|
@@ -1855,6 +1888,44 @@ class VrpcClient extends EventEmitter {
|
|
|
1855
1888
|
})
|
|
1856
1889
|
}
|
|
1857
1890
|
|
|
1891
|
+
/**
|
|
1892
|
+
* Waits for the answer with id `id`.
|
|
1893
|
+
*
|
|
1894
|
+
* @param {String} id
|
|
1895
|
+
* @param {Object} waiter
|
|
1896
|
+
* @param {String} waiter.agent the agent that answers
|
|
1897
|
+
* @param {Function} waiter.lose called with a reason when the answer can
|
|
1898
|
+
* no longer arrive
|
|
1899
|
+
* @param {Boolean} [waiter.second] a promise's result, the second answer
|
|
1900
|
+
* to its call
|
|
1901
|
+
* @param {Function} onAnswer called with the answer
|
|
1902
|
+
* @returns {Function} stops waiting, without a call to `lose`
|
|
1903
|
+
*/
|
|
1904
|
+
_awaitAnswer (id, { agent, lose, second = false }, onAnswer) {
|
|
1905
|
+
const waiter = { agent, lose, second }
|
|
1906
|
+
const listener = data => {
|
|
1907
|
+
this._waiting.delete(waiter)
|
|
1908
|
+
onAnswer(data)
|
|
1909
|
+
}
|
|
1910
|
+
waiter.forget = () => {
|
|
1911
|
+
this._waiting.delete(waiter)
|
|
1912
|
+
this._eventEmitter.removeListener(id, listener)
|
|
1913
|
+
}
|
|
1914
|
+
this._waiting.add(waiter)
|
|
1915
|
+
this._eventEmitter.once(id, listener)
|
|
1916
|
+
return waiter.forget
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
// the answers that can no longer arrive: their calls reject at once, and
|
|
1920
|
+
// a late answer finds no listener
|
|
1921
|
+
_loseAnswers (selects, reason) {
|
|
1922
|
+
for (const waiter of Array.from(this._waiting)) {
|
|
1923
|
+
if (!selects(waiter)) continue
|
|
1924
|
+
waiter.forget()
|
|
1925
|
+
waiter.lose(reason)
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1858
1929
|
static _prepareError (rpcError) {
|
|
1859
1930
|
if (typeof rpcError === 'object') {
|
|
1860
1931
|
return { message: rpcError.message, cause: rpcError.cause }
|