-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This adds a general interface for abortable operations, and a specific implementation of that interface for use internally. This should simplify the transition to abortable network requests and others. Issue #829 Change-Id: Icf54e23028f7454640f5ec76ca97d8a8ed5915ed
- Loading branch information
1 parent
bd2b163
commit e5afb35
Showing
6 changed files
with
808 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/** | ||
* @license | ||
* Copyright 2016 Google Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
/** @externs */ | ||
|
||
/** | ||
* A representation of an abortable operation. Note that these are not | ||
* cancelable. Cancelation implies undoing what has been done so far, | ||
* whereas aborting only means that futher work is stopped. | ||
* | ||
* @interface | ||
* @template T | ||
* @exportDoc | ||
*/ | ||
shakaExtern.IAbortableOperation; | ||
|
||
|
||
/** | ||
* @const {!Promise.<T>} | ||
* A Promise which represents the underlying operation. It is resolved when | ||
* the operation is complete, and rejected if the operation fails or is | ||
* aborted. Aborted operations should be rejected with a shaka.util.Error | ||
* object using the error code OPERATION_ABORTED. | ||
* @exportDoc | ||
*/ | ||
shakaExtern.IAbortableOperation.prototype.promise; | ||
|
||
|
||
/** | ||
* Can be called by anyone holding this object to abort the underlying | ||
* operation. This is not cancelation, and will not necessarily result in | ||
* any work being undone. abort() should return a Promise which is resolved | ||
* when the underlying operation has been aborted. The returned Promise | ||
* should never be rejected. | ||
* | ||
* @return {!Promise} | ||
* @exportDoc | ||
*/ | ||
shakaExtern.IAbortableOperation.prototype.abort = function() {}; | ||
|
||
|
||
/** | ||
* @param {function()} onFinal A callback to be invoked after the operation | ||
* succeeds or fails. | ||
* @return {!shakaExtern.IAbortableOperation.<T>} Returns this. | ||
* @exportDoc | ||
*/ | ||
shakaExtern.IAbortableOperation.prototype.finally = function(onFinal) {}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,244 @@ | ||
/** | ||
* @license | ||
* Copyright 2016 Google Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
goog.provide('shaka.util.AbortableOperation'); | ||
|
||
goog.require('shaka.util.Error'); | ||
goog.require('shaka.util.PublicPromise'); | ||
|
||
/** | ||
* A utility to wrap abortable operations. Note that these are not cancelable. | ||
* Cancelation implies undoing what has been done so far, whereas aborting only | ||
* means that futher work is stopped. | ||
* | ||
* @implements {shakaExtern.IAbortableOperation.<T>} | ||
* @template T | ||
*/ | ||
shaka.util.AbortableOperation = class { | ||
/** | ||
* @param {!Promise.<T>} promise | ||
* A Promise which represents the underlying operation. It is resolved when | ||
* the operation is complete, and rejected if the operation fails or is | ||
* aborted. Aborted operations should be rejected with a shaka.util.Error | ||
* object using the error code OPERATION_ABORTED. | ||
* @param {function():!Promise} onAbort | ||
* Will be called by this object to abort the underlying operation. | ||
* This is not cancelation, and will not necessarily result in any work | ||
* being undone. abort() should return a Promise which is resolved when the | ||
* underlying operation has been aborted. The returned Promise should never | ||
* be rejected. | ||
*/ | ||
constructor(promise, onAbort) { | ||
/** @const {!Promise.<T>} */ | ||
this.promise = promise; | ||
|
||
/** @private {function():!Promise} */ | ||
this.onAbort_ = onAbort; | ||
|
||
/** @private {boolean} */ | ||
this.aborted_ = false; | ||
} | ||
|
||
/** | ||
* @param {!shaka.util.Error} error | ||
* @return {!shaka.util.AbortableOperation} An operation which has already | ||
* failed with the error given by the caller. | ||
*/ | ||
static failed(error) { | ||
return new shaka.util.AbortableOperation( | ||
Promise.reject(error), | ||
Promise.resolve); | ||
} | ||
|
||
/** | ||
* @return {!shaka.util.AbortableOperation} An operation which has already | ||
* failed with the error OPERATION_ABORTED. | ||
*/ | ||
static aborted() { | ||
return new shaka.util.AbortableOperation( | ||
Promise.reject(new shaka.util.Error( | ||
shaka.util.Error.Severity.CRITICAL, | ||
shaka.util.Error.Category.PLAYER, | ||
shaka.util.Error.Code.OPERATION_ABORTED)), | ||
Promise.resolve); | ||
} | ||
|
||
/** | ||
* @param {U} value | ||
* @return {!shaka.util.AbortableOperation.<U>} An operation which has already | ||
* completed with the given value. | ||
* @template U | ||
*/ | ||
static completed(value) { | ||
return new shaka.util.AbortableOperation( | ||
Promise.resolve(value), | ||
Promise.resolve); | ||
} | ||
|
||
/** | ||
* @param {!Promise.<U>} promise | ||
* @return {!shaka.util.AbortableOperation.<U>} An operation which cannot be | ||
* aborted. It will be completed when the given Promise is resolved or | ||
* failed when the given Promise is rejected. | ||
* @template U | ||
*/ | ||
static notAbortable(promise) { | ||
return new shaka.util.AbortableOperation( | ||
promise, | ||
// abort() here will return a Promise which is resolved when the input | ||
// promise either resolves or fails. | ||
() => promise.catch(() => {})); | ||
} | ||
|
||
/** @override */ | ||
abort() { | ||
this.aborted_ = true; | ||
return this.onAbort_(); | ||
} | ||
|
||
/** | ||
* @param {!Array.<!shaka.util.AbortableOperation>} operations | ||
* @return {!shaka.util.AbortableOperation} An operation which is resolved | ||
* when all operations are successful, fails if any operation fails, and | ||
* for which abort() aborts all given operations. | ||
*/ | ||
static all(operations) { | ||
return new shaka.util.AbortableOperation( | ||
Promise.all(operations.map((op) => op.promise)), | ||
() => Promise.all(operations.map((op) => op.abort()))); | ||
} | ||
|
||
/** | ||
* @override | ||
*/ | ||
finally(onFinal) { | ||
this.promise.then((value) => onFinal(), (e) => onFinal()); | ||
return this; | ||
} | ||
|
||
/** | ||
* @param {(undefined| | ||
* function(T):U| | ||
* function(T):!Promise.<U>| | ||
* function(T):!shaka.util.AbortableOperation.<U>)} onSuccess | ||
* A callback to be invoked after this operation is complete, to chain to | ||
* another operation. The callback can return a plain value, a Promise to | ||
* an asynchronous value, or another AbortableOperation. | ||
* @param {function(*)=} onError | ||
* An optional callback to be invoked if this operation fails, to perform | ||
* some cleanup or error handling. Analogous to the second parameter of | ||
* Promise.prototype.then. | ||
* @return {!shaka.util.AbortableOperation.<U>} An operation which is resolved | ||
* when this operation and the operation started by the callback are | ||
* complete. | ||
* @template U | ||
*/ | ||
chain(onSuccess, onError) { | ||
var newPromise = new shaka.util.PublicPromise(); | ||
|
||
// If called before "this" completes, just abort "this". | ||
var abort = () => { | ||
newPromise.reject(new shaka.util.Error( | ||
shaka.util.Error.Severity.CRITICAL, | ||
shaka.util.Error.Category.PLAYER, | ||
shaka.util.Error.Code.OPERATION_ABORTED)); | ||
return this.abort(); | ||
}; | ||
|
||
this.promise.then((value) => { | ||
if (this.aborted_) { | ||
// If "this" is not abortable(), or if abort() is called after "this" | ||
// is complete but before the next stage in the chain begins, we should | ||
// stop right away. | ||
newPromise.reject(new shaka.util.Error( | ||
shaka.util.Error.Severity.CRITICAL, | ||
shaka.util.Error.Category.PLAYER, | ||
shaka.util.Error.Code.OPERATION_ABORTED)); | ||
return; | ||
} | ||
|
||
if (!onSuccess) { | ||
// No callback? Pass the success along. | ||
newPromise.resolve(value); | ||
return; | ||
} | ||
|
||
// Call the success callback, interpret the return value, | ||
// set the Promise state, and get the next abort function. | ||
abort = shaka.util.AbortableOperation.wrapChainCallback_( | ||
onSuccess, value, newPromise); | ||
}, (e) => { | ||
// "This" failed or was aborted. | ||
|
||
if (!onError) { | ||
// No callback? Pass the failure along. | ||
newPromise.reject(e); | ||
return; | ||
} | ||
|
||
// Call the error callback, interpret the return value, | ||
// set the Promise state, and get the next abort function. | ||
abort = shaka.util.AbortableOperation.wrapChainCallback_( | ||
onError, e, newPromise); | ||
}); | ||
|
||
return new shaka.util.AbortableOperation( | ||
newPromise, | ||
// By creating a closure around abort(), we can update the value of | ||
// abort() at various stages. | ||
() => abort()); | ||
} | ||
|
||
/** | ||
* @param {(function(T):U| | ||
* function(T):!Promise.<U>| | ||
* function(T):!shaka.util.AbortableOperation.<U>)| | ||
* function(*)} callback | ||
* A callback to be invoked with the given value. | ||
* @param {T} value | ||
* @param {!shaka.util.PublicPromise} newPromise The promise for the next | ||
* stage in the chain. | ||
* @return {function():!Promise} The next abort() function for the chain. | ||
* @private | ||
* @template T, U | ||
*/ | ||
static wrapChainCallback_(callback, value, newPromise) { | ||
try { | ||
var ret = callback(value); | ||
|
||
if (ret && ret.promise && ret.abort) { | ||
// This is an abortable operation, with its own abort() method. | ||
// After this point, abort() should abort the operation from the | ||
// callback, and the new promise should be tied to the promise | ||
// from the callback's operation. | ||
newPromise.resolve(ret.promise); | ||
return () => ret.abort(); | ||
} else { | ||
// This is a Promise or a plain value, and this step cannot be aborted. | ||
newPromise.resolve(ret); | ||
// Abort is complete when the returned value/Promise is resolved or | ||
// fails, but never fails itself nor returns a value. | ||
return () => Promise.resolve(ret).then(() => {}).catch(() => {}); | ||
} | ||
} catch (exception) { | ||
// The callback threw an exception or error. Reject the new Promise and | ||
// resolve any future abort call right away. | ||
newPromise.reject(exception); | ||
return Promise.resolve; | ||
} | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.