【问题标题】:Race condition in Backbone when fetching data in unit tests在单元测试中获取数据时,Backbone 中的竞争条件
【发布时间】:2014-11-20 00:42:15
【问题描述】:

我们正在使用 Backbone(较差)和 Handlebars、Mocha 和 SinonJS 进行测试。在尝试对其他开发人员代码进行单元测试时,我不断遇到问题。模型或集合获取数据时似乎存在问题。进入我的单元测试时,异步调用并不总是完成,所以我经常在构建服务器上看到这些误报。

开发人员没有使用 LayoutManager 或任何其他工具来管理视图生命周期。我可以在视图中添加afterRender 函数以确保加载所有数据吗?如果是这样,该功能会是什么样子?我会利用 jQuery 的承诺吗?

我尝试在测试的 beforeEach 方法中模拟请求并调用 done 函数,但问题似乎仍然存在。有人可以指出一些可以帮助我确保在进入测试之前呈现视图并完成获取的内容吗?任何帮助都会很棒。谢谢!

【问题讨论】:

    标签: javascript jquery unit-testing backbone.js race-condition


    【解决方案1】:

    我遇到了同样的问题,这就是我所做的。

    回调助手

    // The Callbacks module.
    //
    // A simple way of managing a collection of callbacks
    // and executing them at a later point in time, using jQuery's
    // `Deferred` object.
    // 
    // Source: https://github.com/derickbailey/backbone.marionette/blob/master/lib/backbone.marionette.js#L1291
    define(['jquery'], function($) {
    
        function Callbacks() {
            this.deferred = $.Deferred();
            this.promise = this.deferred.promise();
        }
    
        Callbacks.prototype = {
            constructor: Callbacks,
    
            // Add a callback to be executed. Callbacks added here are
            // guaranteed to execute, even if they are added after the
            // `run` method is called.
            add: function(callback, contextOverride) {
                this.promise.done(function(context, options) {
                    if (contextOverride) {
                        context = contextOverride;
                    }
    
                    callback.call(context, options);
                });
            },
    
            // Run all registered callbacks with the context specified.
            // Additional callbacks can be added after this has been run
            // and they will still be executed.
            run: function(options, context) {
                this.deferred.resolve(context, options);
            }
        };
    
    
        return Callbacks;
    });
    

    核心合集

    在这个集合中,我们有 2 个重要的方法:onReset()collectionReset()onReset() 方法应该是 Views“监听”集合的 reset 事件的方式,因为它使用 Promises。

    collectionReset() 方法仅在集合的 reset 事件被触发时执行,此时我们认为集合已加载并且我们解析所有添加的回调.

    这会处理竞争条件,因为回调是使用 onReset() 方法添加的,即使在添加回调之前触发了事件时也会执行。

    当你创建一个新集合并让它继承这个集合时,你必须确保在获取时添加reset选项:(new ExampleCollection()).fetch({ reset: true })

    // The Core Collection - other collections inherit from this one.
    // Source: http://lostechies.com/derickbailey/2012/02/03/get-a-model-from-a-backbone-collection-without-knowing-if-the-collection-is-loaded
    define([
        'jquery',
        'backbone',
        'app/callbacks'
    ], function($, Backbone, Callbacks) {
    
        var CoreCollection = Backbone.Collection.extend({
            constructor: function() {
                Backbone.Collection.prototype.constructor.apply(this, [].slice.call(arguments));
    
                this.onResetCallbacks = new Callbacks();
                this.on('reset', this.collectionReset, this);
            },
    
            // The `onReset` method should be called by views to render collection data
            // when the particular collection has been loaded from the server.
            onReset: function(callback, contextOverride) {
                this.onResetCallbacks.add(callback, contextOverride);
    
                if (this.loaded) {
                    this.onResetCallbacks.run(this);
                }
            },
    
            // Since this method is called only on the collection's `reset`
            // event, we assume that the collection has been fully loaded.
            collectionReset: function() {
                if (!this.loaded) {
                    this.loaded = true;
                }
    
                // Execute all the `onResetCallbacks` callbacks.
                this.onResetCallbacks.run(this);
            }
        });
    
        return CoreCollection;
    });
    

    示例视图

    在示例视图中,您将执行以下操作:

    define([
        'jquery',
        'backbone',
        'handlebars',
    ],
    function($, Backbone, Handlebars) {
        var ExampleView = Backbone.View.extend({
            id: 'example-view',
            template: Handlebars.templates.example,
    
            events: {
            },
    
            initialize: function() {
                // If it renders a collection from the server.
                this.collection = __collection_name__;
                this.collection.onReset(function() {
                    // It's guaranteed to execute.
                    // No race condition anymore!
                    // ...
                });
            }
        });
    
    
        return ExampleView;
    });
    

    【讨论】:

    • 很好的解释!很彻底!我一定会利用它!
    猜你喜欢
    • 1970-01-01
    • 2011-01-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-02
    • 2017-02-20
    • 2012-04-08
    • 2016-10-10
    相关资源
    最近更新 更多