假设我正在编写以下函数:
async function foo(axe: Axe): Promise<Sword> {
// ...
}
它的用法是这样的:
async function bar() {
// get an axe somehow ...
const sword = await foo(axe);
// do something with the sword ...
}
到目前为止,一切顺利。问题是,为了实现foo,我想调用下面的“回调风格的异步”函数。我不能改变它的签名(它是一个库/模块):
/* The best way to get a sword from an axe!
* Results in a null if axe is not sharp enough */
function qux(axe: Axe, callback: (sword: Sword) => void);
我发现的最好的方法是"promisify" qux:
async function foo(axe: Axe): Promise<Sword> {
return new Promise<Sword>((resolve, reject) => {
qux(axe, (sword) => {
if (sword !== null) {
resolve(sword);
} else {
reject('Axe is not sharp enough ;(');
}
});
});
}
它工作,但我希望我能做一些更直接/可读性。在某些语言中,您可以创建一个类似promise的对象(我在这里称之为Assure
),然后在其他地方显式地设置其值。就像这样:
async function foo(axe: Axe): Promise<Sword> {
const futureSword = new Assure<Sword>();
qux((sword) => {
if (sword !== null) {
futureSword.provide(sword);
} else {
futureSword.fail('Axe is not sharp enough ;(');
}
});
return futureSword.promise;
这在语言本身中是可能的吗?或者我需要使用一个库/模块,比如deferred?
更新1:额外动机
为什么人们更喜欢第二种解决方案而不是第一种?由于回调链接。
如果我想在foo中执行多个步骤,而不仅仅是调用qux,该怎么办?如果这是同步代码,它可能看起来像这样:
function zim(sling: Sling): Rifle {
const bow = bop(sling);
const crossbow = wug(bow);
const rifle = kek(crossbow);
return rifle;
}
如果这些函数是异步的,promisify-ing会给予我这个:
async function zim(sling: Sling): Promise<Rifle> {
return new Promise<Rifle>((resolve, reject) => {
bop(sling, (bow) => {
wug(bow, (crossbow) => {
kek(crossbow, (rifle) => {
resolve(rifle);
});
});
});
);
}
使用Assure
,我可以这样做:
async function zim(sling: Sling): Promise<Rifle> {
const futureBow = new Assure<Bow>();
bop(sling, (bow) => futureBow.provide(bow));
const futureCrossbow = new Assure<Crossbow>();
wug(await futureBow, (crossbow) => futureCrossbow.provide(crossbow));
const futureRifle = new Assure<Rifle>();
kek(await futureCrossbow, (rifle) => futureRifle.provide(rifle));
return futureRifle;
}
我发现这更易于管理,因为我不需要跟踪嵌套的作用域,也不需要担心计算的顺序。如果函数有多个参数,则差异会更大。
反射
也就是说,我同意嵌套调用的版本是优雅的,因为我们不需要声明所有这些临时变量。
在写这个问题的时候,我有了另一个想法,关于如何与JavaScript的精神保持一致:
function zim(sling: Sling): Rifle {
const bow = await new Promise((resolve, reject) => { bop(sling, resolve); });
const crossbow = await new Promise((resolve, reject) => { wug(bow, resolve); });
const rifle = await new Promise((resolve, reject) => { kek(crossbow, resolve); });
return rifle;
}
...这开始看起来很像从Nodejs使用util.promisify
。如果回调遵循错误优先的约定就好了。..但在这一点上,似乎有理由实现一个朴素的myPromisify
,它 Package 了promisify
并处理我所拥有的回调类型。
1条答案
按热度按时间jhdbpxl91#
Assure
可以很简单地在TypeScript中实现:带演示的Playground Link
话虽如此,它可能是不需要的。其用法最终基本上类似于使用promise,然而:
1.它不是惯用的。
1.与常规承诺相比,似乎没有带来任何价值。
一个更好的替代方案可能是定义一个函数,可以将回调函数转换为承诺返回函数。这也不需要太多的工作--使用TS,我们甚至可以确保转换后的函数具有正确的类型。下面是主要类型的回调函数的实现:
它允许这样的用法:
Playground链接
使用promisify函数,
foo()
可以简单地实现为:Playground链接
这里对
p_qux
的赋值仅用于演示目的。更惯用的代码可以在函数外进行一次赋值,或者直接使用const maybeSword = await promisifyResultCallback(qux)(axe);
如果真的需要,promising函数可以被改变为允许传递参数,并允许像
promisifyResultCallback(qux, axe)
这样的用法,但是,这留给读者练习。使用promise也消除了嵌套问题:
一旦功能被承诺:
可以作为常规的promise处理:
或
或
Playground链接