-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathPromiseQueue.js
166 lines (143 loc) · 4.59 KB
/
PromiseQueue.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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// gr: now as a module. As we convert the web api to modules.
// native is still not module-friendly though, so some of this will be duplicated
// in PopApi.js still
// a promise queue that manages multiple listeners
// gr: this is getting out of sync with the cyclic-fixing-copy in WebApi. Make it seperate!
export default class PromiseQueue
{
constructor(DebugName='UnnamedPromiseQueue',QueueWarningSize=100,Warning)
{
this.Warning = Warning || function(){};
this.QueueWarningSize = QueueWarningSize;
this.Name = DebugName;
// pending promises
this.Promises = [];
// values we've yet to resolve (each is array capturing arguments from push()
this.PendingValues = [];
}
async WaitForNext()
{
const Promise = this.Allocate();
// if we have any pending data, flush now, this will return an already-resolved value
this.FlushPending();
return Promise;
}
// this waits for next resolve, but when it flushes, it returns LAST entry and clears the rest; LIFO (kinda, last in, only out)
async WaitForLatest()
{
const Promise = this.Allocate();
// if we have any pending data, flush now, this will return an already-resolved value
this.FlushPending(true);
return Promise;
}
ClearQueue()
{
// delete values, losing data!
this.PendingValues = [];
}
// allocate a promise, maybe deprecate this for the API WaitForNext() that makes more sense for a caller
Allocate()
{
// create a promise function with the Resolve & Reject functions attached so we can call them
function CreatePromise()
{
let Callbacks = {};
let PromiseHandler = function (Resolve,Reject)
{
Callbacks.Resolve = Resolve;
Callbacks.Reject = Reject;
}
let Prom = new Promise(PromiseHandler);
Prom.Resolve = Callbacks.Resolve;
Prom.Reject = Callbacks.Reject;
return Prom;
}
const NewPromise = CreatePromise();
this.Promises.push( NewPromise );
return NewPromise;
}
// put this value in the queue, if its not already there (todo; option to choose oldest or newest position)
PushUnique(Value)
{
const Args = Array.from(arguments);
function IsMatch(PendingValue)
{
// all arguments are now .PendingValues=[] or .RejectionValues=[]
// we are only comparing PendingValues, lets allow rejections to pile up as
// PushUnique wont be rejections. The Reject() code should have a RejectUnique() if this becomes the case
if (!PendingValue.hasOwnProperty('ResolveValues'))
return false;
const a = PendingValue.ResolveValues;
const b = Args;
if ( a.length != b.length ) return false;
for ( let i=0; i<a.length; i++ )
if ( a[i] != b[i] )
return false;
return true;
}
// skip adding if existing match
if ( this.PendingValues.some(IsMatch) )
{
//this.Warning(`Skipping non-unique ${Args}`);
return;
}
this.Push(...Args);
}
Push()
{
const Args = Array.from(arguments);
const Value = {};
Value.ResolveValues = Args;
this.PendingValues.push( Value );
if ( this.PendingValues.length > this.QueueWarningSize )
this.Warning(`This (${this.Name}) promise queue has ${this.PendingValues.length} pending values and ${this.Promises.length} pending promises`,this);
this.FlushPending();
}
GetQueueSize()
{
return this.PendingValues.length;
}
HasPending()
{
return this.PendingValues.length > 0;
}
FlushPending(FlushLatestAndClear=false)
{
// if there are promises and data's waiting, we can flush next
if ( this.Promises.length == 0 )
return;
if ( this.PendingValues.length == 0 )
return;
// flush 0 (FIFO)
// we pre-pop as we want all listeners to get the same value
if (FlushLatestAndClear && this.PendingValues.length > 1)
{
this.Warning(`Promise queue FlushLatest dropping ${this.PendingValues.length - 1} elements`);
}
const Value0 = FlushLatestAndClear ? this.PendingValues.splice(0,this.PendingValues.length).pop() : this.PendingValues.shift();
const HandlePromise = function(Promise)
{
if ( Value0.RejectionValues )
Promise.Reject( ...Value0.RejectionValues );
else
Promise.Resolve( ...Value0.ResolveValues );
}
// pop array incase handling results in more promises, so we avoid infinite loop
const Promises = this.Promises.splice(0);
// need to try/catch here otherwise some will be lost
Promises.forEach( HandlePromise );
}
Resolve()
{
throw "PromiseQueue.Resolve() has been deprecated for Push() to enforce the pattern that we're handling a queue of values";
}
// reject all the current promises
Reject()
{
const Args = Array.from(arguments);
const Value = {};
Value.RejectionValues = Args;
this.PendingValues.push(Value);
this.FlushPending();
}
}