我有一个验证字符串的方法,我希望该方法返回Promise,因为正在运行的验证可能是异步的。但是,我遇到的问题是性能问题,我希望在可能的情况下(例如:当没有异步验证要完成时)在同一事件循环中解决承诺,但是我希望接口保持一致(例如:始终返回一个承诺)。
下面的简化代码示例说明了我正在尝试执行的操作,但是它会产生上述性能损失,因为即使可以同步执行验证,它仍然会等待下一个事件循环来处理结果。
在我的特定用例中,此性能损失太高了。
下面是我正在做的简化(最小)示例
// Array containing validation methods
const validations = [
(value) => true, // Some validation would happen here
];
// Array containing asynchronous validation methods
const asyncValidations = []; // No async validations (but there could be)
const validate(value){
// Run synchronous validations
try {
validations.forEach(validation => validation(value));
catch(error){
// Synchronous validation failed
return Promise.reject();
}
if(asyncValidations){
return Promise.all(asyncValidations.map(validation => validation(value));
}
// Otherwise return a resolved promise (to provide a consistent interface)
return Promise.resolve(); // Synchronous validation passed
}
// Example call
validate('test').then(() => {
// Always asynchronously called
});
答案 0 :(得分:4)
您提到两件事:
我希望界面保持一致
[我想]总是返回诺言
如果要避免不必要的异步行为,可以这样做,并使API保持一致。但是您不能做的是“总是返回承诺”,因为不可能“同步地解决承诺”。
您的代码当前返回一个Promise,当不需要进行异步验证时,该Promise将被解决:
// Otherwise return a resolved promise (to provide a consistent interface)
return Promise.resolve(); // Synchronous validation passed
您可以使用以下代码替换该代码:
return {then: cb => cb()};
请注意,这只会返回“ thenable”(即具有then
方法)的对象文字,并且会同步执行传递给它的回调。但是,它不不返回承诺。
答案 1 :(得分:2)
promise异步解析的原因是为了确保它们不会破坏堆栈。考虑以下使用promise的堆栈安全代码。
console.time("promises");
let promise = Promise.resolve(0);
for (let i = 0; i < 1e7; i++) promise = promise.then(x => x + 1);
promise.then(x => {
console.log(x);
console.timeEnd("promises");
});
如您所见,即使创建了1000万个中间承诺对象,它也不会炸毁堆栈。但是,由于它正在处理下一个刻度的每个回调,因此在我的笔记本电脑上大约需要5秒钟来计算结果。您的里程可能会有所不同。
您可以在不影响性能的情况下确保堆栈安全吗?
是的,您可以但不能承诺。期间,无法同步解决承诺。因此,我们需要其他一些数据结构。以下是一个这样的数据结构的实现。
// type Unit = IO ()
// data Future a where
// Future :: ((a -> Unit) -> Unit) -> Future a
// Future.pure :: a -> Future a
// Future.map :: (a -> b) -> Future a -> Future b
// Future.apply :: Future (a -> b) -> Future a -> Future b
// Future.bind :: Future a -> (a -> Future b) -> Future b
const Future = f => ({ constructor: Future, f });
Future.pure = x => ({ constructor: Future.pure, x });
Future.map = (f, x) => ({ constructor: Future.map, f, x });
Future.apply = (f, x) => ({ constructor: Future.apply, f, x });
Future.bind = (x, f) => ({ constructor: Future.bind, x, f });
// data Callback a where
// Callback :: (a -> Unit) -> Callback a
// Callback.map :: (a -> b) -> Callback b -> Callback a
// Callback.apply :: Future a -> Callback b -> Callback (a -> b)
// Callback.bind :: (a -> Future b) -> Callback b -> Callback a
const Callback = k => ({ constructor: Callback, k });
Callback.map = (f, k) => ({ constructor: Callback.map, f, k });
Callback.apply = (x, k) => ({ constructor: Callback.apply, x, k });
Callback.bind = (f, k) => ({ constructor: Callback.bind, f, k });
// data Application where
// InFuture :: Future a -> Callback a -> Application
// Apply :: Callback a -> a -> Application
const InFuture = (f, k) => ({ constructor: InFuture, f, k });
const Apply = (k, x) => ({ constructor: Apply, k, x });
// runApplication :: Application -> Unit
const runApplication = _application => {
let application = _application;
while (true) {
switch (application.constructor) {
case InFuture: {
const {f: future, k} = application;
switch (future.constructor) {
case Future: {
application = null;
const {f} = future;
let async = false, done = false;
f(x => {
if (done) return; else done = true;
if (async) runApplication(Apply(k, x));
else application = Apply(k, x);
});
async = true;
if (application) continue; else return;
}
case Future.pure: {
const {x} = future;
application = Apply(k, x);
continue;
}
case Future.map: {
const {f, x} = future;
application = InFuture(x, Callback.map(f, k));
continue;
}
case Future.apply: {
const {f, x} = future;
application = InFuture(f, Callback.apply(x, k));
continue;
}
case Future.bind: {
const {x, f} = future;
application = InFuture(x, Callback.bind(f, k));
continue;
}
}
}
case Apply: {
const {k: callback, x} = application;
switch (callback.constructor) {
case Callback: {
const {k} = callback;
return k(x);
}
case Callback.map: {
const {f, k} = callback;
application = Apply(k, f(x));
continue;
}
case Callback.apply: {
const {x, k} = callback, {x: f} = application;
application = InFuture(x, Callback.map(f, k));
continue;
}
case Callback.bind: {
const {f, k} = callback;
application = InFuture(f(x), k);
continue;
}
}
}
}
}
};
// inFuture :: Future a -> (a -> Unit) -> Unit
const inFuture = (f, k) => runApplication(InFuture(f, Callback(k)));
// Example:
console.time("futures");
let future = Future.pure(0);
for (let i = 0; i < 1e7; i++) future = Future.map(x => x + 1, future);
inFuture(future, x => {
console.log(x);
console.timeEnd("futures");
});
如您所见,性能比使用诺言更好。我的笔记本电脑大约需要4秒钟。你的旅费可能会改变。但是,更大的优点是每个回调都可以同步调用。
解释此代码的工作方式不在此问题的范围内。我试图尽可能简洁地编写代码。阅读它应该提供一些见识。
关于我如何编写这样的代码,我从以下程序开始,然后手动执行了许多编译器优化。我执行的优化是{{3}}和通过defunctionalization进行的尾部调用优化。
const Future = inFuture => ({ inFuture });
Future.pure = x => Future(k => k(x));
Future.map = (f, x) => Future(k => x.inFuture(x => k(f(x))));
Future.apply = (f, x) => Future(k => f.inFuture(f => x.inFuture(x => k(f(x)))));
Future.bind = (x, f) => Future(k => x.inFuture(x => f(x).inFuture(k)));
最后,我建议您检查trampolining库。它做类似的事情,具有实用程序功能来与诺言进行转换,允许您取消期货,并支持顺序和并行期货。
答案 2 :(得分:0)
只需将所有同步验证器转换为异步验证器?
// only accept number > 10 and < 20
const validations = [
value => value > 10,
value => value < 20,
value => true
];
// always resolve, cost 0.5 seconds
const asyncValidations = [
value => new Promise(resolve => setTimeout(resolve, 500))
];
const validate = value => {
// convert all sync validators into async
const asyncfyValidations = validations.map(validation => (value => new Promise((resolve, reject) => (validation(value) ? resolve : reject)())));
// combine two groups of validators and get the result
return Promise.all([...asyncfyValidations, ...asyncValidations].map(validation => validation(value)));
}
// done in 0.5 seconds
validate(15).then(() => {
console.log('test 1 done');
}).catch(ex => {
console.log('test 1 failed');
});
// fails immediately
validate(9).then(() => {
console.log('test 2 done');
}).catch(ex => {
console.log('test 2 failed');
});
答案 3 :(得分:0)
从技术上讲,当返回一个promise或其他内容时,可以完全相同的方式访问该函数:
function test(returnPromise=false) {
return returnPromise ? new Promise(resolve=>resolve('Hello asynchronous World!')) : 'Hello synchronous World!'
}
async function main() {
const testResult1 = await test(false)
console.log(testResult1)
const testResult2 = await test(true)
console.log(testResult2)
}
main().catch(console.error)
为此,您必须将所有代码放入任何异步函数中。但是无论函数是否返回promise,都可以使用await。