【问题标题】:How to implement asynchronous computed observable with multiple $.ajax calls?如何使用多个 $.ajax 调用实现异步计算 observable?
【发布时间】:2014-11-04 15:07:54
【问题描述】:

我正在尝试实现一个异步计算的 observable,如 show here

我可以通过一个 ajax 调用成功地做到这一点。我目前面临的挑战是如何在循环中执行各种 ajax 调用,以异步方式构建数组,然后使用 jQuery Promise 将数组返回到我计算的可观察数组。

基本上,HTML 表单的工作方式如下:

  1. 这是一个学生课程表。
  2. 对于每一行,用户输入人员编号,然后在另一列输入以逗号分隔的课程 ID 列表。例如 100、200、300。
  3. 计算的 observable 的目的是存储一个数组 包含在第 2 步中输入的课程的课程详细信息。
  4. 通过为每个课程触发 ajax 调用并将 HTTP 响应存储在数组中来获取详细信息。
  5. 我不希望用户等待结果,因此是实现异步计算 observable 的原因。

我的问题:我在将最终数组的值返回到 observable 时遇到问题。它总是未定义的。 ajax 调用工作正常,但也许我仍然没有正确处理承诺。

这是我班级的代码:

function asyncComputed(evaluator, owner) {
            var result = ko.observable(), currentDeferred;
            result.inProgress = ko.observable(false); // Track whether we're waiting for a result

            ko.computed(function () {
                // Abort any in-flight evaluation to ensure we only notify with the latest value
                if (currentDeferred) { currentDeferred.reject(); }

                var evaluatorResult = evaluator.call(owner);
                // Cope with both asynchronous and synchronous values
                if (evaluatorResult && (typeof evaluatorResult.done == "function")) { // Async
                    result.inProgress(true);
                    currentDeferred = $.Deferred().done(function (data) {
                        result.inProgress(false);
                        result(data);
                    });
                    evaluatorResult.done(currentDeferred.resolve);
                } else // Sync
                    result(evaluatorResult);
            });

            return result;
        }


        function personDetails(id, personNumber, courseIds) {
            var self = this;
            self.id = ko.observable(id);
            self.personNumber = ko.observable(personNumber);
            self.courseIds = ko.observable(courseIds);

            // Computed property to extract PIC details for additional PICs.
            // This is computed observable which returns response asynchronously
            self.courseDetails = asyncComputed(function () {
                var courseIdsArray = self.courseIds().split(",");
                var arr = [];
                var arr_promises = [];

                function getCourseDetails(courseId) {
                    var dfrd = $.Deferred();
                    var content = {};

                    content.searchString = courseId;

                    var url = 'MyURL';

                    return $.ajax(url, {
                        type: 'POST',
                        dataType: 'json',
                        data: requestData, // content of requestData is irrelevant. The ajax call works fine.
                        processdata: true,
                        cache: false,
                        async: true,
                        contentType: "application/json"
                    }).done(function (data) {
                        arr.push(new PicDetails(data.GenericIdentifierSearchResult[0]));
                    }).fail(function () {
                        alert("Could not retrieve PIC details");
                    }).then(function () {
                        dfrd.resolve();
                    });

                }

                if (courseIdsArray.length > 0) {

                    $.each(courseIdsArray, function (index, courseId) {
                        if (courseId.length > 0) {
                            arr_promises.push(getCourseDetails(courseId));
                        }
                    });
                };

                $.when.apply($, arr_promises).done(function () {
                    return arr;
                })

            }, this);
        } 

【问题讨论】:

    标签: javascript jquery arrays ajax knockout.js


    【解决方案1】:

    我认为您实际上并不需要单独的 api/代码。

    您可以为您网站上更改的每个输入/值创建可观察对象,并基于这些创建计算的可观察对象。

    例如粗略的伪代码

    self.id           = ko.observable(id);
    self.personNumber = ko.observable(personNumber);
    self.courseIds    = ko.observable(courseIds);
    self.courseDetailsArray = ko.observableArray([]);
    self.courseDetails = ko.computed(function() {
        //computed the course details based on other observables
        //whenever user types in more course ids, start loading them
        $.get( yoururl, {self.courseIds and self.id}).success(data) {
            when finished async loading, parse the data and push the new course details into final array
            self.courseDetailsArray.push( your loaded and parsed data );
            //since courseDetailsArray is observableArray, you can have further computed observables     using and re-formatting it.
        }
    });
    

    【讨论】:

    • 谢谢@Rainer Plumer - 我遇到的问题是我无法使用所有课程 ID 进行一次 ajax 调用。我必须为每个课程 ID 调用 Web 服务并将详细信息存储在数组中。只有在数组完成后,我才应该将它返回给计算出的 observable。计算出的 observable 的值取决于多个 ajax 调用。电话的数量将取决于课程的数量。
    • 为什么要等到所有的id都加载完?为什么不将加载的数据一一推送到可观察数组中。唯一的缺点/(或好处)是每次添加新项目时都会重新渲染用户界面。这可能是好的(用户看到加载进度)或坏的(页面速度)。速度不应该是一个问题,因为用户不能真正快速地输入 id 以产生显着的性能影响。您还可以为可观察更新定义延迟,以便它不会经常更新..让您有时间加载其他项目。
    【解决方案2】:

    我的方法与您的方法有些不同,但如果您愿意,您可以使用它构建类似 asyncComputed 的东西:

    1. 制作一个简单的 observable 来保存结果
    2. 制作一个承诺字典,您将基本上与课程 ID 数组保持同步
    3. 当课程 ID 数组发生变化时,从 Promise 字典中添加/删除
    4. 将您的所有承诺包装在 when 中(就像您正在做的那样)并在它们全部完成后设置结果

    基本思路:

    var results = ko.observable([]);
    var loadingPromises = {};
    var watcher = ko.computed(function () {
    
        var ids = ko.unwrap(listOfIds);
    
        if (ids && ids.length) {
            ids.forEach(function (id) {
                if (!loadingPromises.hasOwnProperty(id)) {
                    loadingPromises[id] = $.get(url, {...id...});
                }
            });
    
            var stillApplicablePromises = {};
            var promises = []; // we could delete from loadingPromises but v8 optimizes delete poorly
            Object.getOwnPropertyNames(loadingPromises).forEach(function (id) {
                if (ids.indexOf(id) >= 0) {
                    stillApplicablePromises[id] = loadingPromises[id];
                    promises.push(loadingPromises[id]);
                }
            });
            loadingPromises = stillApplicablePromises;
    
            $.when.apply(this, promises).then(function () {
                // process arguments here however you like, they're the responses to your promises
                results(arguments);
            });
    
        } else {
            loadingPromises = {};
            results([]);
        }
    }, this);
    

    这是您可以在“现实生活”中看到的文件(可能会更改):https://github.com/wikimedia/analytics-dashiki/blob/master/src/components/wikimetrics-visualizer/wikimetrics-visualizer.js

    还有基本的小提琴:http://jsfiddle.net/xtsekb20/1/

    【讨论】:

    • 谢谢@Milimetric - 我会试试你的方法并告诉你。
    • 我对 loadingPromises[id] = $.get(url, {...id...}); 有疑问你返回一个承诺到 loadingPromises[id] 吗?
    • 是的,$.get() 就像 $.ajax() 返回一个承诺对象。因此,如果您将其分配给loadingPromises[id],那么您对该 url / id 参数有一个承诺,直到它从listOfIds 中取出。这样,当用户仅通过添加新 id 来更改 listOfIds 时,旧的 Promise 仍然存在并且不会被重新获取。
    • 是不是类似于 $.ajax(url, {...data...}).then(function (data) { return data[0]};
    • 在你上面写的代码中,你通过调用.then 来使用promise。在我上面建议的代码中,你保存了 Promise,这样你就可以同时将所有 Promise 传递给 $.when。还是我误会了你的误会?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多