Skip to content

Commit ffaf288

Browse files
committed
[js] More adjustments to promise callback tracking.
Fixes #1215
1 parent 94a787f commit ffaf288

File tree

5 files changed

+75
-34
lines changed

5 files changed

+75
-34
lines changed

javascript/node/selenium-webdriver/CHANGES.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
## v2.49.0-dev
1+
## v2.48.2-dev
22

33
* Added `WebElement#takeScreenshot()`.
4+
* More adjustments to promise callback tracking.
45

56
## v2.48.1
67

javascript/node/selenium-webdriver/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "selenium-webdriver",
3-
"version": "2.49.0-dev",
3+
"version": "2.48.2-dev",
44
"description": "The official WebDriver JavaScript bindings from the Selenium project",
55
"license": "Apache-2.0",
66
"keywords": [

javascript/webdriver/promise.js

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -215,11 +215,35 @@
215215
* // D
216216
* // fin
217217
*
218+
* Callbacks attached to an _unresolved_ promise within a task function are
219+
* only weakly scheduled as subtasks and will be dropped if they reach the
220+
* front of the queue before the promise is resolved. In the example below, the
221+
* callbacks for `B` & `D` are dropped as sub-tasks since they are attached to
222+
* an unresolved promise when they reach the front of the task queue.
223+
*
224+
* var d = promise.defer();
225+
* flow.execute(function() {
226+
* flow.execute( () => console.log('A'));
227+
* d.promise.then(() => console.log('B'));
228+
* flow.execute( () => console.log('C'));
229+
* d.promise.then(() => console.log('D'));
230+
*
231+
* setTimeout(d.fulfill, 20);
232+
* }).then(function() {
233+
* console.log('fin')
234+
* });
235+
* // A
236+
* // C
237+
* // fin
238+
* // B
239+
* // D
240+
*
218241
* If a promise is resolved while a task function is on the call stack, any
219-
* previously registered callbacks (i.e. attached while the task was _not_ on
220-
* the call stack), act as _interrupts_ and are inserted at the front of the
221-
* task queue. If multiple promises are fulfilled, their interrupts are enqueued
222-
* in the order the promises are resolved.
242+
* previously registered and unqueued callbacks (i.e. either attached while no
243+
* task was on the call stack, or previously dropped as described above) act as
244+
* _interrupts_ and are inserted at the front of the task queue. If multiple
245+
* promises are fulfilled, their interrupts are enqueued in the order the
246+
* promises are resolved.
223247
*
224248
* var d1 = promise.defer();
225249
* d1.promise.then(() => console.log('A'));
@@ -228,17 +252,23 @@
228252
* d2.promise.then(() => console.log('B'));
229253
*
230254
* flow.execute(function() {
231-
* flow.execute(() => console.log('C'));
255+
* d1.promise.then(() => console.log('C'));
232256
* flow.execute(() => console.log('D'));
257+
* });
258+
* flow.execute(function() {
259+
* flow.execute(() => console.log('E'));
260+
* flow.execute(() => console.log('F'));
233261
* d1.fulfill();
234262
* d2.fulfill();
235263
* }).then(function() {
236264
* console.log('fin');
237265
* });
266+
* // D
238267
* // A
239-
* // B
240268
* // C
241-
* // D
269+
* // B
270+
* // E
271+
* // F
242272
* // fin
243273
*
244274
* Within a task function (or callback), each step of a promise chain acts as
@@ -321,24 +351,6 @@
321351
* // F
322352
* // fin
323353
*
324-
* __Note__: while the ControlFlow will wait for
325-
* {@linkplain webdriver.promise.ControlFlow#execute tasks} and
326-
* {@linkplain webdriver.promise.Promise#then callbacks} to complete, it
327-
* _will not_ wait for unresolved promises created within a task:
328-
*
329-
* flow.execute(function() {
330-
* var p = new promise.Promise(function(fulfill) {
331-
* setTimeout(fulfill, 100);
332-
* });
333-
* p.then(() => console.log('promise resolved!'));
334-
* flow.execute(() => console.log('sub-task!'));
335-
* }).then(function() {
336-
* console.log('task complete!');
337-
* });
338-
* // sub-task!
339-
* // task complete!
340-
* // promise resolved!
341-
*
342354
* Finally, consider the following:
343355
*
344356
* var d = promise.defer();
@@ -2628,9 +2640,13 @@ var TaskQueue = goog.defineClass(EventEmitter, {
26282640
return;
26292641
}
26302642
promise.callbacks_.forEach(function(cb) {
2643+
cb.blocked = false;
2644+
if (cb.queue) {
2645+
return;
2646+
}
2647+
26312648
cb.promise[CANCEL_HANDLER_SYMBOL] = this.onTaskCancelled_.bind(this, cb);
26322649

2633-
cb.blocked = false;
26342650
if (cb.queue === this && this.tasks_.indexOf(cb) !== -1) {
26352651
return;
26362652
}

javascript/webdriver/test/promise_flow_test.js

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,7 +1248,7 @@ function testWait_unboundedWaitOnPromiseResolution() {
12481248
messages.push('a');
12491249
}, 5);
12501250

1251-
webdriver.promise.delayed(10).then(function() {
1251+
timeout(10).then(function() {
12521252
assertArrayEquals(['a'], messages);
12531253
assertTrue(waitResult.isPending());
12541254
d.fulfill(1234);
@@ -2110,6 +2110,7 @@ function testCancellingAPendingTask() {
21102110

21112111
unresolved.promise.thenCatch(function(e) {
21122112
order.push(4);
2113+
assertTrue(e instanceof webdriver.promise.CancellationError);
21132114
});
21142115

21152116
return timeout(10).then(function() {
@@ -2120,7 +2121,7 @@ function testCancellingAPendingTask() {
21202121
return waitForIdle();
21212122
}).then(function() {
21222123
assertFlowHistory('a', 'a.1', 'b');
2123-
assertArrayEquals([1, 4, 3], order);
2124+
assertArrayEquals([1, 3, 4], order);
21242125
});
21252126
}
21262127

@@ -2339,3 +2340,26 @@ function testTasksDoNotWaitForNewlyCreatedPromises() {
23392340
assertArrayEquals(['b', 'a', 'c', 'd', 'e', 'fin'], order);
23402341
});
23412342
}
2343+
2344+
function testCallbackDependenciesDoNotDeadlock() {
2345+
var order = [];
2346+
var root = webdriver.promise.defer();
2347+
var dep = webdriver.promise.fulfilled().then(function() {
2348+
order.push('a');
2349+
return root.promise.then(function() {
2350+
order.push('b');
2351+
});
2352+
});
2353+
// This callback depends on |dep|, which depends on another callback
2354+
// attached to |root| via a chain.
2355+
root.promise.then(function() {
2356+
order.push('c');
2357+
return dep.then(() => order.push('d'));
2358+
}).then(() => order.push('fin'));
2359+
2360+
setTimeout(() => root.fulfill(), 20);
2361+
2362+
return waitForIdle().then(function() {
2363+
assertArrayEquals(['a', 'b', 'c', 'd', 'fin'], order);
2364+
});
2365+
}

javascript/webdriver/test/webdriver_test.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,12 +1146,12 @@ function testCustomFunctionDoesNotCompleteUntilReturnedPromiseIsResolved() {
11461146

11471147
// timeout to ensure the first function starts its execution before we
11481148
// trigger d's callbacks.
1149-
webdriver.promise.delayed(0).then(function() {
1149+
return new Promise(f => setTimeout(f, 0)).then(function() {
11501150
assertArrayEquals(['a'], order);
11511151
d.fulfill();
1152-
});
1153-
return waitForIdle().then(function() {
1154-
assertArrayEquals(['a', 'b', 'c'], order);
1152+
return waitForIdle().then(function() {
1153+
assertArrayEquals(['a', 'b', 'c'], order);
1154+
});
11551155
});
11561156
}
11571157

0 commit comments

Comments
 (0)