我正在构建keepassxc webextension的抽象层。它使用redux-saga通道使外观chrome messaging同步。它工作(非)令人惊讶的好。但是我想要完全抽象redux-saga,它看起来像正常函数返回Promise。
TL;博士
KeePassXC-browser将是浏览器扩展程序,允许从浏览器中检索存储在KeePassXC应用程序中的密码。 有两种可能的通信协议:HTTP和NativeClient。所以我决定使用typescript接口,根据通信协议,将有两个实现此接口的类。
接口:
interface Keepass {
getDatabaseHash(): Promise<string>;
getCredentials(origin: string, formUrl: string): Promise<KeepassCredentials[]>;
associate(): Promise<KeepassAssociation>;
isAssociated(dbHash: string): Promise<boolean>;
}
表示HTTP通信协议的First implementation正在使用fetch api,它已经基于Promise,因此实现是直接的,100%符合此接口。
代表NativeClient协议的Second implementation使用redux-saga(效果和通道)使异步消息传递看起来像同步函数调用。它有点复杂,但工作得很好并且涵盖边缘情况,这很难以任何其他方式处理,因为native messaging是基于标准输入和标准输出流的协议,因此请求和响应可以交错,订购等......
实际问题我没有解决,是第二个实现没有实现接口,因为它的生成器不是Promises。
基本上想用返回Promise的函数转换(换行)saga迭代器函数。有一个很好的co library基本上可以为普通的生成器做到这一点。但似乎不适用于redux传奇。
function* someGenerator() {
const state = yield select(); // execution freeze here when called from wrapper
const result = yield call(someEffect);
return result;
}
function wrapper() {
return co(someGenerator); // returns Promise
}
这可能吗?如果是这样,我做错了什么?
答案 0 :(得分:0)
Redux-saga基于生成器函数的特殊原因 - 允许将分离的异步操作分离为已分离的部分,并从位于内部saga进程管理器的一个端点管理它们。相反,在一般情况下,Promise是一个自我的东西,不能被部分执行。换句话说,Promises管理它们所在的控制流,而生成器由外部控制流管理。
yield select(); //从包装器调用时执行冻结
您的主要误解是假设select
实际执行了一些异步操作。不,它只是在该点上暂停函数somegenatator
并将控制转移到redux-saga引擎,后者知道与返回值有关,并且可能表示异步进程(可能没有 - 这没关系)
当进程完成时,saga引擎恢复生成器,并将返回值传递给它。
您可以在select
(https://github.com/redux-saga/redux-saga/blob/master/src/internal/io.js#L139)的源代码中轻松查看。它只返回一个具有某种结构的对象,可以通过saga引擎理解,然后引擎执行实际操作,并以generatorName.next(resultValue)
格式调用您的生成器。
UPD。从理论上说,你可以把它包装成可重新分配的承诺,但它不是可用的案例
// Your library code
function deferredPromise() {
let resolver = null;
const promise = new Promise(resolve => (resolver = resolve));
return [
resolver,
promise
];
}
function generateSomeGenerator() {
let [ selectDoneResolve, selectDonePromise ] = deferredPromise();
const someGenetator = function* () {
const state = yield select(); // execution freeze here when called from wrapper
const [newSelectDoneResolve, newSelectDonePromise] = deferredPromise();
selectDoneResolve({
info: state, nextPromise: newSelectDonePromise
});
selectDoneResolve = newSelectDoneResolve;
selectDonePromise = newSelectDonePromise;
const result = yield call(someEffect);
return result;
}
return {
someGenetator,
selectDonePromise
};
}
const { someGenetator: someGenetatorImpl, selectDonePromise } = generateSomeGenerator();
export const someGenetator = someGenetatorImpl;
// Wrapper for interface
selectDonePromise.then(watchDone)
function watchDone({ info, nextPromise }) {
// Do something with your info
nextPromise.then(watchDone);
}