同步承诺解析(bluebird vs. jQuery)

thu*_*uld 12 javascript jquery asynchronous promise bluebird

我为Dynamics CRM REST/ODATA webservice(CrmRestKit)开发了一个小型lib .lib依赖于jQuery并使用promise模式,后者是jQuery的类似promise的模式.

现在我想将此lib移植到bluebird并删除jQuery依赖项.但是我遇到了一个问题,因为bluebird不支持promise-objects的同步解析.

一些上下文信息:

CrmRestKit的API除了可选参数之外,该参数定义是否应该以同步或异步模式执行Web服务调用:

CrmRestKit.Create( 'Account', { Name: "foobar" }, false ).then( function ( data ) {
   ....
} );
Run Code Online (Sandbox Code Playgroud)

当您传递"true"或省略最后一个参数时,该方法是否会同步创建记录.模式.

有时需要在同步模式下执行操作,例如,您可以为Dynamics CRM编写JavaScript代码,该代码是为表单的保存事件调用的,在此事件处理程序中,您需要执行同步操作以进行验证(例如,验证是否存在一定数量的子记录,如果存在正确数量的记录,则取消保存操作并显示错误消息).

我现在的问题如下:bluebird不支持同步模式下的分辨率.例如,当我执行以下操作时,以"异步"方式调用"then"处理程序:

function print( text ){

    console.log( 'print -> %s', text );

    return text;
}

///
/// 'Promise.cast' cast the given value to a trusted promise. 
///
function getSomeTextSimpleCast( opt_text ){

    var text = opt_text || 'Some fancy text-value';

    return Promise.cast( text );
}

getSomeTextSimpleCast('first').then(print);
print('second');
Run Code Online (Sandbox Code Playgroud)

输出如下:

print -> second
print -> first
Run Code Online (Sandbox Code Playgroud)

我希望"第二个"出现在"第一个"之后,因为承诺已经用一个值来解决.所以我假设当应用于已经解析的promise-object时会立即调用then-event-handler .

当我使用jQuery执行相同的操作(然后使用已经解决的承诺)时,我将获得预期的结果:

function jQueryResolved( opt_text ){

    var text = opt_text || 'jQuery-Test Value',
    dfd =  new $.Deferred();

    dfd.resolve(text);

        // return an already resolved promise
    return dfd.promise();
}

jQueryResolved('third').then(print);
print('fourth');
Run Code Online (Sandbox Code Playgroud)

这将生成以下输出:

print -> third
print -> fourth
Run Code Online (Sandbox Code Playgroud)

有没有办法让蓝鸟以同样的方式工作?

更新: 提供的代码只是为了说明问题.lib的思想是:无论执行模式(sync,async)如何,调用者总是会处理promise-object.

关于"...询问用户......似乎没有任何意义":当您提供两种方法"CreateAsync"和"CreateSync"时,用户也可以决定如何执行操作.

无论如何,使用当前实现的默认行为(最后一个参数是可选的)是异步执行.因此,99%的代码需要一个promise-object,可选参数仅用于只需要同步执行的1%的情况.此外,我为自己开发了lib,我在99,9999%的情况下使用了异步模式,但我认为可以随心所欲地选择同步路.

但我认为我得到的一点是同步方法应该只返回值.对于下一个版本(3.0),我将实现"CreateSync"和"CreateAsync".

感谢您的输入.

Update-2 我对可选参数的强调是确保一致性行为并防止出现逻辑错误.假设您是使用lib的我的方法"GetCurrentUserRoles"的消费者.因此,该方法将总是返回一个promise,这意味着您必须使用"then"方法来执行依赖于结果的代码.所以当有人写这样的代码时,我同意这是完全错误的:

var currentUserRoels = null;

GetCurrentUserRoles().then(function(roles){

    currentUserRoels = roles;
});

if( currentUserRoels.indexOf('foobar') === -1 ){

    // ...
}
Run Code Online (Sandbox Code Playgroud)

我同意当"GetCurrentUserRoles"方法从同步更改为异步时,此代码将中断.

但我明白这不是一个好设计,因为消费者现在应该处理异步方法.

Joe*_*han 18

简短版本:我明白你想要这样做,但答案是否定的.

我认为要问的根本问题是,如果承诺已经完成,完成的承诺是否应立即进行回调.我可以想到很多可能会发生这种情况的原因 - 例如,异步保存过程只会在进行更改时保存数据.它可能能够以同步方式检测来自客户端的变化,而不必经过外部资源,但是如果检测到变化,则只需要进行异步操作.

在其他具有异步调用的环境中,模式似乎是开发人员有责任了解他们的工作可能会立即完成(例如,.NET框架的异步模式实现可以适应这种情况).这不是框架的设计问题,而是它的实现方式.

JavaScript的开发人员(以及上面的许多评论者)似乎对此有不同的观点,坚持认为如果某些东西可能是异步的,它必须始终是异步的.这是否"正确"是不重要的 - 根据我在https://promisesaplus.com/上找到的规范,第2.2.4项规定,基本上没有回调可以被调用,直到你没有我所指的作为"脚本代码"或"用户代码"; 也就是说,规范明确指出,即使承诺已经完成,您也无法立即调用回调.我已经检查过其他几个地方,他们要么就此话题一无所知,要么同意原始来源.我不知道是否https://promisesaplus.com/ 在这方面可以被认为是最终的信息来源,但我认为没有其他来源不同意,而且似乎是最完整的.

这个限制有些武断,我坦率地更喜欢.NET的观点.我将把它留给其他人来决定他们是否认为"坏代码"做一些可能会或可能不会以看起来异步的方式同步的东西.

您的实际问题是Bluebird是否可以配置为执行非JavaScript行为.在性能方面,这样做可能会带来一些小好处,而在JavaScript中,如果你足够努力就可以做任何事情,但随着Promise对象在各个平台上变得越来越普遍,你会看到转向使用它作为本机组件而不是自定义编写polyfill或库.因此,无论今天的答案是什么,在Bluebird中重新修改承诺可能会在将来导致问题,并且您的代码可能不应该依赖于或立即解决承诺.


Den*_*ret 8

您可能认为这是一个问题,因为没有办法

getSomeText('first').then(print);
print('second');
Run Code Online (Sandbox Code Playgroud)

并且在分辨率同步getSomeText "first"之前打印"second".

但我认为你有一个逻辑问题.

如果您的getSomeText函数可能是同步的异步的,则根据上下文,它不应该影响执行的顺序.你使用promises来确保它始终是一样的.具有可变执行顺序可能会成为应用程序中的错误.

使用

getSomeText('first') // may be synchronous using cast or asynchronous with ajax
.then(print)
.then(function(){ print('second') });
Run Code Online (Sandbox Code Playgroud)

在这两种情况下(同步cast或异步解析),您将拥有正确的执行顺序.

请注意,使某个函数有时是同步的,有时不是同步的(不管是缓存处理还是池化).你只需假设它是异步的,一切都会好起来的.

但是如果你不想离开JavaScript的领域(即如果你不使用一些本机代码),如果他希望操作是异步的,那么要求API的用户精确地使用布尔参数似乎没有任何意义. ).

  • 真正重要的一点是,根据承诺立即执行代码是一个错误.没有同步解决承诺将帮助您避免这种错误. (2认同)

Flo*_*ine 7

承诺的目的是使异步代码更容易,即更接近使用同步代码时的感觉.

您正在使用同步代码.不要让它变得更复杂.

function print( text ){

    console.log( 'print -> %s', text );

    return text;
}

function getSomeTextSimpleCast( opt_text ){

    var text = opt_text || 'Some fancy text-value';

    return text;
}

print(getSomeTextSimpleCast('first'));
print('second');
Run Code Online (Sandbox Code Playgroud)

这应该是它的结束.


如果你想保持相同的异步接口,即使你的代码是同步的,那么你必须一直这样做.

getSomeTextSimpleCast('first')
    .then(print)
    .then(function() { print('second'); });
Run Code Online (Sandbox Code Playgroud)

then从正常的执行流程中获取代码,因为它应该是异步的.蓝鸟在那里做得很好.它的作用的简单解释:

function then(fn) {
    setTimeout(fn, 0);
}
Run Code Online (Sandbox Code Playgroud)

请注意,bluebird并不是真的这样做,它只是给你一个简单的例子.

试试吧!

then(function() {
    console.log('first');
});
console.log('second');
Run Code Online (Sandbox Code Playgroud)

这将输出以下内容:

second
first 
Run Code Online (Sandbox Code Playgroud)

  • @Brandon你在说什么?如果你有同步函数不使用promises - 如果你有异步函数jQuery延迟创建_race conditions_通过异步运行_sometimes_另一方面_Bluebird总是以相同的方式运行_无论种族如何. (2认同)