有什么办法可以同步解决承诺吗? (或可以的替代库)

时间:2019-08-01 09:15:09

标签: javascript ecmascript-6 promise es6-promise

我有一个验证字符串的方法,我希望该方法返回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
});

4 个答案:

答案 0 :(得分:4)

您提到两件事:

  1.   

    我希望界面保持一致

  2.   

    [我想]总是返回诺言

如果要避免不必要的异步行为,可以这样做,并使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方法)的对象文字,并且会同步执行传递给它的回调。但是,它不返回承诺。

您还可以通过实现then方法和/或catch方法的可选onRejected参数来扩展此方法。

答案 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。