有没有办法同步解决承诺? (或可以的替代库)
Is there any way to resolve a promise synchronously? (or an alternative library that can)
我有一个验证字符串的方法,我希望该方法 return Promise 因为验证是 运行 可能 是异步的。然而,我遇到的问题是性能之一,我希望承诺尽可能在同一个事件循环中解决(例如:当没有异步验证要完成时)但我希望接口保持一致(例如:始终return 一个承诺)。
下面的简化代码示例说明了我正在尝试做的事情,但它会导致上述性能损失,因为即使可以同步执行验证,它仍然会等待下一个事件循环来处理结果。
在我的特定用例中,此性能损失太高。
下面是我正在做的一个简化(最小)示例
// 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
});
你提到了两件事:
I want the interface to remain consistent
[I want to] always return a Promise
如果你想避免不需要的异步行为,你可以这样做并保持 API 一致。但是你不能做的是 "always return a Promise" 因为不可能 "resolve a promise synchronously".
您的代码目前 return 是一个在不需要异步验证时解析的 Promise:
// Otherwise return a resolved promise (to provide a consistent interface)
return Promise.resolve(); // Synchronous validation passed
您可以用以下代码替换该代码:
return {then: cb => cb()};
请注意,这只是 return 一个对象文字,它是 "thenable"(即它有一个 then
方法)并且将 同步 执行你传递给它的任何回调。然而,它不是return一个承诺。
您还可以通过实施 then
method and/or the the catch
方法的可选 onRejected
参数来扩展此方法。
从技术上讲,当 returns 承诺或其他内容时,可以以完全相同的方式访问函数:
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)
尽管如此,您必须将所有代码放入任何异步函数中。但是你可以只使用 await,不管函数 returns 是否是一个 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 秒来计算结果。您的里程可能会有所不同。
你能在不影响性能的情况下实现堆栈安全吗?
是的,你可以但不能承诺。 Promise 不能同步解决,期间。因此,我们需要一些其他的数据结构。以下是一种此类数据结构的实现。
// 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");
});
如您所见,性能比使用 promises 好一点。在我的笔记本电脑上大约需要 4 秒。你的旅费可能会改变。不过更大的好处是每个回调都是同步调用的。
解释这段代码的工作原理超出了这个问题的范围。我尽量把代码写得干净利落。阅读它应该会提供一些见解。
至于我是怎么想到写这样的代码的,我是从下面的程序开始的,然后手工进行了一堆编译器优化。我执行的优化是 defunctionalization and tail call optimization via trampolining.
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)));
最后,我鼓励您查看 Fluture 库。它做类似的事情,具有实用功能来转换承诺和从承诺中转换,允许您取消期货,并支持顺序期货和并行期货。
我有一个验证字符串的方法,我希望该方法 return Promise 因为验证是 运行 可能 是异步的。然而,我遇到的问题是性能之一,我希望承诺尽可能在同一个事件循环中解决(例如:当没有异步验证要完成时)但我希望接口保持一致(例如:始终return 一个承诺)。
下面的简化代码示例说明了我正在尝试做的事情,但它会导致上述性能损失,因为即使可以同步执行验证,它仍然会等待下一个事件循环来处理结果。
在我的特定用例中,此性能损失太高。
下面是我正在做的一个简化(最小)示例
// 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
});
你提到了两件事:
I want the interface to remain consistent
[I want to] always return a Promise
如果你想避免不需要的异步行为,你可以这样做并保持 API 一致。但是你不能做的是 "always return a Promise" 因为不可能 "resolve a promise synchronously".
您的代码目前 return 是一个在不需要异步验证时解析的 Promise:
// Otherwise return a resolved promise (to provide a consistent interface)
return Promise.resolve(); // Synchronous validation passed
您可以用以下代码替换该代码:
return {then: cb => cb()};
请注意,这只是 return 一个对象文字,它是 "thenable"(即它有一个 then
方法)并且将 同步 执行你传递给它的任何回调。然而,它不是return一个承诺。
您还可以通过实施 then
method and/or the the catch
方法的可选 onRejected
参数来扩展此方法。
从技术上讲,当 returns 承诺或其他内容时,可以以完全相同的方式访问函数:
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)
尽管如此,您必须将所有代码放入任何异步函数中。但是你可以只使用 await,不管函数 returns 是否是一个 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 秒来计算结果。您的里程可能会有所不同。
你能在不影响性能的情况下实现堆栈安全吗?
是的,你可以但不能承诺。 Promise 不能同步解决,期间。因此,我们需要一些其他的数据结构。以下是一种此类数据结构的实现。
// 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");
});
如您所见,性能比使用 promises 好一点。在我的笔记本电脑上大约需要 4 秒。你的旅费可能会改变。不过更大的好处是每个回调都是同步调用的。
解释这段代码的工作原理超出了这个问题的范围。我尽量把代码写得干净利落。阅读它应该会提供一些见解。
至于我是怎么想到写这样的代码的,我是从下面的程序开始的,然后手工进行了一堆编译器优化。我执行的优化是 defunctionalization and tail call optimization via trampolining.
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)));
最后,我鼓励您查看 Fluture 库。它做类似的事情,具有实用功能来转换承诺和从承诺中转换,允许您取消期货,并支持顺序期货和并行期货。