gpt4 book ai didi

javascript - 使用promise的语法编写同步代码会不会有任何好处

转载 作者:行者123 更新时间:2023-12-04 05:17:45 24 4
gpt4 key购买 nike

有同步 promise 的概念吗?使用promise语法编写同步代码是否有好处?

try {
foo();
bar(a, b);
bam();
} catch(e) {
handleError(e);
}

...可以写成类似(但使用 then的同步版本);
foo()
.then(bar.bind(a, b))
.then(bam)
.fail(handleError)

最佳答案

Is there such a concept as a synchronous promise?



本杰明是绝对正确的。 Promises are a type of monad。但是,它们不是唯一的类型。

如果您还不知道它,那您可能想知道什么是单子(monad)。网上有很多有关单子(monad)的解释。但是,大多数人患有 monad tutorial fallacy

简而言之,谬误是大多数了解单子(monad)的人并不真正知道如何向他人解释这一概念。简单来说,单子(monad)是一个抽象概念,人类很难掌握抽象概念。但是,人类容易轻视具体概念。

因此,让我们开始从一个具体的概念入手,了解单子(monad)。如我所说,单子(monad)是一个抽象的概念。这意味着monad是没有 interfaceimplementation(即,它定义了某些操作并指定了这些操作应执行的操作,而未指定必须完成的操作)。

现在,有不同类型的单子(monad)。每种monad都是具体的(即,它定义monad implementationinterface)。 promise 是一种单子(monad)。因此,promise是monad的具体示例。因此,如果我们研究 promise ,那么我们就可以开始理解单子(monad)。

那么我们从哪里开始呢?幸运的是,用户 spike在我们的 comment中为您的问题提供了一个很好的起点:

One instance I can think of is chaining promises together with sync code. While finding an answer for this question: Generating AJAX Request Dynamically Based on Scenario I wrapped a synchronous call in a promise in order to be able to chain them with other promises.



因此,让我们看一下他的代码:
var run = function() {
getScenario()
.then(mapToInstruction)
.then(waitForTimeout)
.then(callApi)
.then(handleResults)
.then(run);
};

此处 run函数返回一个 promise ,该 promise 由链在一起的 getScenariomapToInstructionwaitForTimeoutcallApihandleResultsrun本身返回的 promise 组成。

现在,在我们继续之前,我想向您介绍一个新的表示法,以可视化这些功能的作用:

run              :: Unit        -> Deferred a
getScenario :: Unit -> Deferred Data
mapToInstruction :: Data -> Deferred Instruction
waitForTimeout :: Instruction -> Deferred Instruction
callApi :: Instruction -> Deferred Data
handleResults :: Data -> Deferred Unit

所以这是细分:
  • ::符号的意思是“属于该类型”,而->符号的意思是“至”。因此,例如run :: Unit -> Deferred a读为“run的类型为UnitDeferred a”。
  • 这意味着run是一个函数,它接受Unit值(即没有参数)并返回Deferred a类型的值。
  • 在这里,a表示任何类型。我们不知道a是什么类型,也不在乎a是什么类型。因此,它可以是任何类型。
  • 在这里,Deferred是一个promise数据类型(具有不同的名称),而Deferred a意味着当promise解析后,它会产生a类型的值。

  • 我们可以从上面的可视化中学到一些东西:
  • 每个函数取一些值并返回一个Promise。
  • 每个promise返回的已解析值将成为下一个函数的输入:

    run              :: Unit -> Deferred a
    getScenario :: Unit -> Deferred Data

    getScenario :: Unit -> Deferred Data
    mapToInstruction :: Data -> Deferred Instruction

    mapToInstruction :: Data -> Deferred Instruction
    waitForTimeout :: Instruction -> Deferred Instruction

    waitForTimeout :: Instruction -> Deferred Instruction
    callApi :: Instruction -> Deferred Data

    callApi :: Instruction -> Deferred Data
    handleResults :: Data -> Deferred Unit

    handleResults :: Data -> Deferred Unit
    run :: Unit -> Deferred a
  • 下一个函数要等到前一个promise解析后才能执行,因为它必须利用前一个promise的解析值。

  • 现在,正如我前面提到的,monad是一个 interface,它定义了某些操作。 monad接口(interface)提供的操作之一是链接monad的操作。在 promise 的情况下,这是 then方法。例如:
    getScenario().then(mapToInstruction)

    我们知道:

    getScenario      :: Unit -> Deferred Data
    mapToInstruction :: Data -> Deferred Instruction

    因此:

    getScenario()    :: Deferred Data -- because when called, getScenario
    -- returns a Deferred Data value

    我们也知道:

    getScenario().then(mapToInstruction) :: Deferred Instruction

    因此,我们可以得出:

    then :: Deferred a -> (a -> Deferred b) -> Deferred b

    换句话说,“ then是一个接受两个参数( Deferred a类型的值和 a -> Deferred b类型的函数)并返回 Deferred b类型的值的函数。”因此:

    then          :: Deferred a    -> (a -> Deferred b) -> Deferred b
    getScenario() :: Deferred Data

    -- Therefore, since a = Data

    getScenario().then :: (Data -> Deferred b) -> Deferred b
    mapToInstruction :: Data -> Deferred Instruction

    -- Therefor, since b = Instruction

    getScenario().then(mapInstruction) :: Deferred Instruction

    因此,我们进行了第一个monad操作:

    then :: Deferred a -> (a -> Deferred b) -> Deferred b

    但是,此操作是具体的。它特定于 promise 。我们想要一个可以对任何monad起作用的抽象操作。因此,我们对函数进行了概括,使其可以适用于任何monad:

    bind :: Monad m => m a -> (a -> m b) -> m b

    注意,此 bind函数与 Function.prototype.bind 没有关系。此 bind函数是 then函数的概括。然后 then函数特定于promise。但是, bind函数是通用的。它可以用于任何monad m

    粗箭头 =>表示 bounded quantification。如果 ab可以是任何类型,则 m可以是实现monad接口(interface)的任何类型。我们不关心 m是什么类型,只要它实现monad接口(interface)即可。

    这就是我们在JavaScript中实现和使用 bind函数的方式:
    function bind(m, f) {
    return m.then(f);
    }

    bind(getScenario(), mapToInstruction);

    这个通用吗?好吧,我可以创建一个实现 then函数的新数据类型:
    // Identity :: a -> Identity a

    function Identity(value) {
    this.value = value;
    }

    // then :: Identity a -> (a -> Identity b) -> Identity b

    Identity.prototype.then = function (f) {
    return f(this.value);
    };

    // one :: Identity Number

    var one = new Identity(1);

    // yes :: Identity Boolean

    var yes = bind(one, isOdd);

    // isOdd :: Number -> Identity Boolean

    function isOdd(n) {
    return new Identity(n % 2 === 1);
    }

    除了 bind(one, isOdd),我还可以轻松编写 one.then(isOdd)(实际上更容易阅读)。

    像promises一样, Identity数据类型也是monad的一种类型。实际上,它是所有单子(monad)中最简单的。之所以称为 Identity,是因为它对输入类型没有任何作用。它保持原样。

    不同的monad具有不同的作用,这使它们有用。例如,promise具有管理异步性的作用。 Identity monad无效。这是原始数据类型。

    无论如何,继续...我们发现了monad的一个操作,即 bind函数。还有一项操作尚待发现。实际上,用户 spike在他前面提到的评论中提到了它:

    I wrapped a synchronous call in a promise in order to be able to chain them with other promises.



    您会发现,问题在于 then函数的第二个参数必须是返回promise的函数:

    then :: Deferred a -> (a -> Deferred b) -> Deferred b
    |_______________|
    |
    -- second argument is a function
    -- that returns a promise

    这意味着第二个参数必须是异步的(因为它返回了一个Promise)。但是,有时我们可能希望将同步函数与 then链接在一起。为此,我们将同步函数的返回值包装在promise中。例如,这是 spike所做的:
    // mapToInstruction :: Data -> Deferred Instruction

    // The result of the previous promise is passed into the
    // next as we're chaining. So the data will contain the
    // result of getScenario
    var mapToInstruction = function (data) {
    // We map it onto a new instruction object
    var instruction = {
    method: data.endpoints[0].method,
    type: data.endpoints[0].type,
    endpoint: data.endpoints[0].endPoint,
    frequency: data.base.frequency
    };

    console.log('Instructions recieved:');
    console.log(instruction);

    // And now we create a promise from this
    // instruction so we can chain it
    var deferred = $.Deferred();
    deferred.resolve(instruction);
    return deferred.promise();
    };

    如您所见, mapToInstruction函数的返回值为 instruction。但是,我们需要将其包装在一个Promise对象中,这就是我们这样做的原因:
    // And now we create a promise from this
    // instruction so we can chain it
    var deferred = $.Deferred();
    deferred.resolve(instruction);
    return deferred.promise();

    实际上,他在 handleResults函数中也做同样的事情:
    // handleResults :: Data -> Deferred Unit

    var handleResults = function(data) {
    console.log("Handling data ...");
    var deferred = $.Deferred();
    deferred.resolve();
    return deferred.promise();
    };

    最好将这三行放在一个单独的函数中,这样我们就不必重复自己了:
    // unit :: a -> Deferred a

    function unit(value) {
    var deferred = $.Deferred();
    deferred.resolve(value);
    return deferred.promise();
    }

    使用此 unit函数,我们可以按以下方式重写 mapToInstructionhandleResults:
    // mapToInstruction :: Data -> Deferred Instruction

    // The result of the previous promise is passed into the
    // next as we're chaining. So the data will contain the
    // result of getScenario
    var mapToInstruction = function (data) {
    // We map it onto a new instruction object
    var instruction = {
    method: data.endpoints[0].method,
    type: data.endpoints[0].type,
    endpoint: data.endpoints[0].endPoint,
    frequency: data.base.frequency
    };

    console.log('Instructions recieved:');
    console.log(instruction);

    return unit(instruction);
    };

    // handleResults :: Data -> Deferred Unit

    var handleResults = function(data) {
    console.log("Handling data ...");
    return unit();
    };

    实际上,事实证明 unit函数是monad接口(interface)的第二个缺失操作。概括后,可以将其可视化如下:

    unit :: Monad m => a -> m a

    它所做的全部工作将值包装为monad数据类型。这使您可以将常规值和函数提升到单子(monad)上下文中。例如,promise提供一个异步上下文,并且 unit允许您将同步函数提升到该异步上下文中。同样,其他monad提供其他效果。

    通过将 unit与函数组合在一起,可以将函数提升到Monadic上下文中。例如,考虑我们之前定义的 isOdd函数:
    // isOdd :: Number -> Identity Boolean

    function isOdd(n) {
    return new Identity(n % 2 === 1);
    }

    如下定义它会更好(尽管会更慢):
    // odd :: Number -> Boolean

    function odd(n) {
    return n % 2 === 1;
    }

    // unit :: a -> Identity a

    function unit(value) {
    return new Identity(value);
    }

    // isOdd :: Number -> Identity Boolean

    function idOdd(n) {
    return unit(odd(n));
    }

    如果我们使用 compose函数,它将看起来更好:
    // compose :: (b -> c) -> (a -> b) -> a -> c
    // |______| |______|
    // | |
    function compose( f, g) {

    // compose(f, g) :: a -> c
    // |
    return function ( x) {
    return f(g(x));
    };
    }

    var isOdd = compose(unit, odd);

    我之前提到过,monad是没有 interfaceimplementation(即它定义了某些操作并指定了这些操作应执行的操作,而未指定必须如何执行)。因此,monad是一个接口(interface),它可以:
  • 定义某些操作。
  • 指定这些操作应执行的操作。

  • 现在我们知道单子(monad)的两个操作是:

    bind :: Monad m => m a -> (a -> m b) -> m b

    unit :: Monad m => a -> m a

    现在,我们将研究这些操作应执行的操作或行为方式(即,我们将研究控制monad的法律):
    // Given:

    // x :: a
    // f :: Monad m => a -> m b
    // h :: Monad m => m a
    // g :: Monad m => b -> m c

    // we have the following three laws:

    // 1. Left identity

    bind(unit(x), f) === f(x)

    unit(x).then(f) === f(x)

    // 2. Right identity

    bind(h, unit) === h

    h.then(unit) === h

    // 3. Associativity

    bind(bind(h, f), g) === bind(h, function (x) { return bind(f(x), g); })

    h.then(f).then(g) === h.then(function (x) { return f(x).then(g); })

    给定数据类型,我们可以为其定义 thenunit函数,这些函数违反了这些法律。在那种情况下, thenunit的那些特定实现是不正确的。

    例如,数组是一种表示非确定性计算的单子(monad)。让我们为数组定义一个错误的 unit函数(数组的 bind函数正确):
    // unit :: a -> Array a

    function unit(x) {
    return [x, x];
    }

    // concat :: Array (Array a) -> Array a

    function concat(h) {
    return h.concat.apply([], h);
    }

    // bind :: Array a -> (a -> Array b) -> Array b

    function bind(h, f) {
    return concat(h.map(f));
    }

    数组的 unit的此错误定义违反了第二定律(正确的标识):
    // 2. Right identity

    bind(h, unit) === h

    // proof

    var h = [1,2,3];

    var lhs = bind(h, unit) = [1,1,2,2,3,3];

    var rhs = h = [1,2,3];

    lhs !== rhs;

    数组的 unit的正确定义是:
    // unit :: a -> Array a

    function unit(x) {
    return [x];
    }

    需要注意的有趣特性是,数组 bind函数是根据 concatmap实现的。但是,数组不是唯一拥有此属性的monad。每个monad bind函数都可以按照 concatmap的广义monadic版本来实现:

    concat :: Array (Array a) -> Array a

    join :: Monad m => m (m a) -> m a

    map :: (a -> b) -> Array a -> Array b

    fmap :: Functor f => (a -> b) -> f a -> f b

    如果您对 functor感到困惑,请不要担心。函子只是实现 fmap函数的数据类型。根据定义,每个monad也是一个函子。

    我将不介绍单子(monad)法则的细节以及 fmapjoin怎么等同于 bind。您可以在 Wikipedia page上阅读有关它们的信息。

    另外,根据 JavaScript Fantasy Land Specificationunit函数称为 of,而 bind函数称为 chain。这将允许您编写如下代码:
    Identity.of(1).chain(isOdd);

    无论如何,回到您的主要问题:

    Would there be any benefit to writing synchronous code using the syntax of promises?



    是的,当使用promise的语法编写同步代码(即monadic代码)时,将会获得很大的好处。许多数据类型都是monad,使用monad接口(interface),您可以对不同类型的顺序计算建模,例如异步计算,非确定性计算,带故障的计算,带状态的计算,带日志的计算等。我最喜欢的使用monad的示例之一是使用 free monads to create language interpreters

    Monad是功能编程语言的功能。使用monad可以促进代码重用。从这个意义上说,这绝对是件好事。但是,这是要付出代价的。功能代码比程序代码慢几个数量级。如果这对您来说不是问题,那么您绝对应该考虑编写monadic代码。

    一些比较流行的monad是数组(用于非确定性计算), Maybe monad(用于可能失败的计算,类似于浮点数中的 NaN)和 monadic parser combinators

    try {
    foo();
    bar(a, b);
    bam();
    } catch(e) {
    handleError(e);
    }

    ...could be written something like (but using a synchronous version of then);

    foo()
    .then(bar.bind(a, b))
    .then(bam)
    .fail(handleError)


    是的,您绝对可以这样编写代码。注意,我没有提到有关 fail方法的任何内容。原因是您根本不需要特殊的 fail方法。

    例如,让我们为可能失败的计算创建一个monad:
    function CanFail() {}

    // Fail :: f -> CanFail f a

    function Fail(error) {
    this.error = error
    }

    Fail.prototype = new CanFail;

    // Okay :: a -> CanFail f a

    function Okay(value) {
    this.value = value;
    }

    Okay.prototype = new CanFail;

    // then :: CanFail f a -> (a -> CanFail f b) -> CanFail f b

    CanFail.prototype.then = function (f) {
    return this instanceof Okay ? f(this.value) : this;
    };

    然后我们定义 foobarbamhandleError:
    // foo :: Unit -> CanFail Number Boolean

    function foo() {
    if (someError) return new Fail(1);
    else return new Okay(true);
    }

    // bar :: String -> String -> Boolean -> CanFail Number String

    function bar(a, b) {
    return function (c) {
    if (typeof c !== "boolean") return new Fail(2);
    else return new Okay(c ? a : b);
    };
    }

    // bam :: String -> CanFail Number String

    function bam(s) {
    if (typeof s !== "string") return new Fail(3);
    else return new Okay(s + "!");
    }

    // handleError :: Number -> Unit

    function handleError(n) {
    switch (n) {
    case 1: alert("unknown error"); break;
    case 2: alert("expected boolean"); break;
    case 3: alert("expected string"); break;
    }
    }

    最后,我们可以如下使用它:
    // result :: CanFail Number String

    var result = foo()
    .then(bar("Hello", "World"))
    .then(bam);

    if (result instanceof Okay)
    alert(result.value);
    else handleError(result.error);

    我描述的 CanFail monad实际上是功能编程语言中的 Either monad。希望能有所帮助。

    关于javascript - 使用promise的语法编写同步代码会不会有任何好处,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28937788/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com