我想创建一个QueryPromise,这只是一个具有取消方法的具体承诺。以下是使用它:
function runQuery(text: string): QueryPromise {
return new QueryPromise((resolve,reject) => {nativeQuery(resolve)})
}
这是我的第一次尝试,希望可以理解:
interface CancellablePromise<T> extends Promise<T> {
cancel: () => void
}
// create the promise my app will use
type QueryPromise = CancellablePromise<string|boolean>
但这还不够。
经过几个小时的反复试验,我设法接近了,但这似乎很乏味,除了干燥外。
interface CancellablePromise<T> extends Promise<T> {
cancel: () => void
}
// also need this interface so the variable can be declared
// plus need to redeclare methods to return the new type
interface CancellablePromiseConstructor extends PromiseConstructor {
new <T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void): CancellablePromise<T>;
cancel: () => void
}
type QueryPromise = CancellablePromise<string|boolean> // for parameter types
var QueryPromise: CancellablePromiseConstructor = Promise // for the new operator
// some example code to exercise the promise
function runQuery(text: string): QueryPromise {
return new QueryPromise((resolve,reject) => {nativeQuery(resolve)})
}
我觉得我走了很长一段路...有人知道这样做的更好方法吗?
TypeScript 接口和类型描述合同。你的很好:
interface CancellablePromise<T> extends Promise<T> {
cancel: () => void
}
type QueryPromise = CancellablePromise<string | boolean>
然后,您可以根据需要实施合同。这是一个示例:
function runQuery(text: string): QueryPromise {
let rejectCb: (err: Error) => void
let p: Partial<QueryPromise> = new Promise<string | boolean>((resolve, reject) => {
rejectCb = reject
/* ... Here the implementation of the query ... */
});
p.cancel = () => {
/* ... Here the implementation of the aborting ... */
rejectCb(new Error("Canceled"))
}
return p as QueryPromise
}
通知:
-
cancel
的实施应拒绝承诺; - 我使用
Partial<QueryPromise>
以后添加成员cancel
。
可以创建一个从承诺继承的类,并在承诺构造函数的执行者参数中添加oncancel:
export class CancellablePromise<T> extends Promise<T> {
private onCancel: () => void
constructor(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void, onCancel: (cancelHandler: () => void) => void) => void) {
let onCancel: () => void;
super((rs, rj) => executor(rs, rj, (ch: () => void) => onCancel = ch));
this.onCancel = onCancel;
}
public cancel() {
if (this.onCancel) {
this.onCancel();
}
}
}
这是用法的示例:
public static search(query: string): CancellablePromise<Region[]> {
return new CancellablePromise((resolve, reject, onCancel) => {
const request = $.ajax({
type: "GET",
url: ROOT_URL + 'api/Regions',
data: { q: query },
success: (regions) => resolve(regions),
error: (jqXHR) => reject(jqXHR.responseText),
});
onCancel(() => {
if (request.state() == "pending") request.abort();
});
});
}
您可以看到,可以像其他任何承诺一样构建此实现。它可以在承诺可以的任何地方使用,并且包括取消方法。
这是取消承诺的示例:
// Cancel previous search (if any)
if (this.currentSearch) {
this.currentSearch.cancel();
}
this.currentSearch = Regions.search(query);
this.currentSearch
.then((regions) => {
if (regions) {
this.setState({ isSearching: false, regions: regions });
} else {
this.setState({ isSearching: false, regions: [] });
}
})
.catch(() => this.setState({ isSearching: false, regions: [] }));
我几个月前实现了类似的东西。我的解决方案与您所需的API完全不匹配,但也许对您有效,或者您可以根据您的需求进行调整:
取消错误
首先,您应该意识到,没有解决且根本不拒绝的诺言是一个坏主意。第三个状态(已取消)将打破现有代码,并且不更改以明确寻找取消的应用程序将永远悬挂。这就是为什么我在取消承诺时拒绝使用特殊Canceled
错误的承诺的原因。通过查看错误类型(使用instanceof
),取消感知的拒绝处理程序可以将故障与取消区分开。取消拒绝处理程序将把取消视为错误。
这是这样的自定义错误类的简单实现:
class Canceled extends Error {
constructor(reason: string = "") {
super(reason);
Object.setPrototypeOf(this, Canceled.prototype);
}
}
需要setPrototypeOf
调用,因此instanceof
可以用于检测此错误类型的实例。
取消接口
接下来,您需要一个可取消承诺的接口。我的几乎就像你的:
interface Cancelable<T> extends Promise<T> {
cancel(reason?: string): Cancelable<T>;
}
取消函数
创建可取消接口的实现类是一个坏主意。很难从内置的承诺类型继承。因此,我决定使用标准的Promise对象,并只需将cancel
方法添加到Promise实例中,而不是创建新类型。为此,我使用此功能:
function cancelable<T>(promise: Promise<T>, onCancel?: (canceled: Canceled) => void): Cancelable<T> {
let cancel: ((reason: string) => Cancelable<T>) | null = null;
let cancelable: Cancelable<T>;
cancelable = <Cancelable<T>>new Promise((resolve, reject) => {
cancel = (reason: string = "") => {
try {
if (onCancel) {
onCancel(new Canceled(reason));
}
} catch (e) {
reject(e);
}
return cancelable;
};
promise.then(resolve, reject);
});
if (cancel) {
cancelable.cancel = cancel;
}
return cancelable;
}
因此,此功能将正常的承诺作为第一个参数,而onCancel
回调为第二个参数,当返回的Cancelable
上调用cancel()
方法时称为第二个参数。
创建可取消的承诺
要实际创建一个可取消的承诺,您必须将标准承诺和取消处理程序与cancelable
功能结合。这是一个示例睡眠函数,在给定时间(使用setTimeout
)之后可以解决,可以取消(用clearTimeout
清除超时):
function sleep(ms: number): Cancelable<void> {
let timer: any;
return cancelable(new Promise(resolve => {
timer = setTimeout(resolve, ms);
}), canceled => {
clearTimeout(timer);
throw canceled;
});
}
请注意,onCancel
回调会收到canceled
参数,该参数是Canceled
错误类型的实例。当承诺成功取消时,onCancel
回调必须丢弃此错误。如果由于某种原因无法取消承诺,那么简单地不要丢失错误,承诺将继续其通常的工作。
如何取消承诺
最后,这是一个示例,如何取消可取消的承诺以及如何在其上做出反应:
const waiting = sleep(1000);
waiting.then(() => {
console.log("Wait successful");
}, error => {
if (error instanceof Canceled) {
console.log("Wait canceled. Reason: " + error.message);
} else {
console.error("Wait failed:", error);
}
});
waiting.cancel("Nah, stop waiting");
我希望这对您有效,或者至少给您一些想法如何进行自己的实施。
@kayahr的解决方案真的很聪明且优雅。但是我不会使用,因为将线程闯入Promise的执行者是非常无礼的。
谁知道,也许有一些IO操作。阅读/写作操作。这种取消可能很危险。我的意思是文件可能不会关闭或类似的东西。
在我的工作中,我还需要一个"可取消"的承诺。我做了其他解决方案。
这个想法是,这不是真正取消的,这是一种获得结果选择的方式。实际的承诺给我们2 1种方法:
- 然后:全部完成,全部确定
- catch :哦,错误
- 最后:做到这一点,只要做
但是,如果我们想停止操作,我们没有这样的选择。
使用此解决方案,这是可能的。
export type TResolver<T> = (value?: T) => void;
export type TRejector = (error: Error) => void;
export type TFinally = () => void;
export type TCanceler<T> = (reason?: T) => void;
export class CancelablePromise<T, C> {
private _resolvers: Array<TResolver<T>> = [];
private _rejectors: TRejector[] = [];
private _cancelers: Array<TCanceler<C>> = [];
private _finishes: TFinally[] = [];
private _canceled: boolean = false;
private _resolved: boolean = false;
private _rejected: boolean = false;
private _finished: boolean = false;
constructor(
executor: (resolve: TResolver<T>, reject: TRejector, cancel: TCanceler<C>, self: CancelablePromise<T, C>) => void,
) {
const self = this;
// Create and execute native promise
new Promise<T>((resolve: TResolver<T>, reject: TRejector) => {
executor(resolve, reject, this._doCancel.bind(this), self);
}).then((value: T) => {
this._doResolve(value);
}).catch((error: Error) => {
this._doReject(error);
});
}
public then(callback: TResolver<T>): CancelablePromise<T, C> {
this._resolvers.push(callback);
return this;
}
public catch(callback: TRejector): CancelablePromise<T, C> {
this._rejectors.push(callback);
return this;
}
public finally(callback: TFinally): CancelablePromise<T, C> {
this._finishes.push(callback);
return this;
}
public cancel(callback: TCanceler<C>): CancelablePromise<T, C> {
this._cancelers.push(callback);
return this;
}
public break(reason: C): CancelablePromise<T, C> {
this._doCancel(reason);
return this;
}
private _doResolve(value: T) {
if (this._canceled) {
return;
}
this._resolved = true;
this._resolvers.forEach((resolver: TResolver<T>) => {
resolver(value);
});
this._doFinally();
}
private _doReject(error: Error) {
if (this._canceled) {
return;
}
this._rejected = true;
this._rejectors.forEach((rejector: TRejector) => {
rejector(error);
});
this._doFinally();
}
private _doFinally() {
if (this._finished) {
return;
}
this._finished = true;
this._finishes.forEach((handler: TFinally) => {
handler();
});
}
private _doCancel(reason?: C) {
if (this._resolved || this._rejected || this._canceled) {
// Doesn't make sence to cancel, because it was resolved or rejected or canceled already
return this;
}
this._canceled = true;
this._cancelers.forEach((cancler: TCanceler<C>) => {
cancler(reason);
});
this._doFinally();
}
}
让我们看看它的工作原理。
const a: CancelablePromise<void, void> = new CancelablePromise<void, void>((resolve, reject, cancel) => {
// Do some long operation
setTimeout(() => {
resolve();
}, 1000);
}).then(() => {
console.log('resolved');
}).finally(() => {
console.log('finally done');
});
几乎就像普通的承诺。在输出中:
resolved
finally done
让我们尝试拒绝:
const b: CancelablePromise<void, void> = new CancelablePromise<void, void>((resolve, reject, cancel) => {
// Do some long operation
setTimeout(() => {
console.log('timer #1');
resolve();
}, 1000);
setTimeout(() => {
console.log('timer #2');
reject(new Error('Because I can!'));
}, 500);
}).then(() => {
console.log('resolved');
}).catch((error: Error) => {
console.log('error');
}).finally(() => {
console.log('finally done');
});
输出:
timer #2
error
finally done
timer #1
现在我们将取消它
const c: CancelablePromise<void, void> = new CancelablePromise<void, void>((resolve, reject, cancel) => {
// Do some long operation
setTimeout(() => {
console.log('timer #1');
resolve();
}, 1000);
setTimeout(() => {
console.log('timer #2');
reject(new Error('Because I can!'));
}, 500);
setTimeout(() => {
console.log('timer #3');
cancel();
}, 250);
}).then(() => {
console.log('resolved');
}).cancel(() => {
console.log('canceled');
}).catch((error: Error) => {
console.log('error');
});
输出
timer #3
timer #2
canceled
timer #1
很好,我们没有解决或拒绝解雇,但是(!)我们的执行者在取消承诺后仍在工作。这不好,这是一种修复并以真实方式取消的方法。
const d: CancelablePromise<void, void> = new CancelablePromise<void, void>((resolve, reject, cancel, self) => {
// Listen cancel inside executer to cancel all correctly
self.cancel(() => {
clearTimeout(t1);
clearTimeout(t2);
});
// Do some long operation
const t1: any = setTimeout(() => {
console.log('timer #1');
resolve();
}, 1000);
const t2: any = setTimeout(() => {
console.log('timer #2');
reject(new Error('Because I can!'));
}, 500);
setTimeout(() => {
console.log('timer #3');
cancel();
}, 250);
}).then(() => {
console.log('resolved');
}).cancel(() => {
console.log('canceled');
}).catch((error: Error) => {
console.log('error');
});
输出
timer #3
canceled
因此,停止了计时器#1和#2 - 仔细取消了承诺。
确保我们也可以在外面取消。
const d: CancelablePromise<void, void> = new CancelablePromise<void, void>((resolve, reject, cancel, self) => {
// Listen cancel inside executer to cancel all correctly
self.cancel(() => {
clearTimeout(t1);
clearTimeout(t2);
});
// Do some long operation
const t1: any = setTimeout(() => {
console.log('timer #1');
resolve();
}, 1000);
const t2: any = setTimeout(() => {
console.log('timer #2');
reject(new Error('Because I can!'));
}, 500);
}).then(() => {
console.log('resolved');
}).cancel(() => {
console.log('canceled');
}).catch((error: Error) => {
console.log('error');
});
setTimeout(() => {
console.log('timer #3 (outside)');
d.break();
}, 250);
结果将相同:
timer #3 (outside)
canceled
希望它将有用。
此实现如何?
declare class CancelablePromise<T> extends Promise<T> {
declare readonly cancel: () => void
}
interface Promise<T> {
asCancelable(): CancelablePromise<T>
}
Promise.prototype.asCancelable = function () {
let cancel: (reason: {cancelled: boolean}) => void
const wrappedPromise = new Promise((resolve, reject) => {
cancel = reject
Promise.resolve(this)
.then(resolve)
.catch(reject)
}) as any
wrappedPromise.cancel = () => {
cancel({cancelled: true})
}
return wrappedPromise as CancelablePromise<unknown>
}
用法:
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
const waiting = sleep(1000).asCancelable()
waiting.then(() => {
console.info('this never get called')
})
waiting.cancel()