使用 AJAX 回调更新挖空可观察数组会生成随机排序的结果

Updating knockout observableArray with AJAX callback produces randomly sorted results

本文关键字:随机 排序 结果 数组 观察 回调 AJAX 更新 使用      更新时间:2023-09-26

这是一些复制该问题的代码

places = ["London","Paris","Brussels"];
placeWikis = ko.observableArray([]);
var viewModel = function(){
for (i in places) {
    function wikiInit(callback){
        this.wikiMarkup = "";
        var wikiTitle = places[i].replace(" ", "_");
        var wikiURL = "https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&exintro=&explaintext=&titles="+places[i];
        $.ajax({
            type:"GET",
            dataType:"jsonp",
            async: false,
            url: wikiURL,
            success: function(result){
                callback(result.query.pages[Object.keys(result.query.pages)[0]].extract);
            },
            error: function(){
            }
        });
    };
    wikiInit(function (wikiString) {
        var wikiMarkup = "<h4>Wikipedia:</h4>" + "<p>" + wikiString + "</p>";
        placeWikis.push(wikiMarkup);
        console.log(placeWikis());
    });
}
ko.applyBindings(new viewModel(), view);

问题是每次此代码运行并从源(在本例中为维基百科(返回数据时,placesWikis(( 的结果都是随机排序的。我在不同的<div>上单独添加 API 结果,所以我需要结果的索引与其各自条目的索引相同[]

您有两个订单问题:

  • for.in不保证订单;
  • 您在回调中异步填充observableArray,但不能保证它们会到达,以便您触发它们!

后者可能是最突出的问题。您当前将结果push到可观察数组中,但您按照浏览器返回结果的顺序执行此操作。

这是对您的问题的稍微最少的重现,其中差异回调时间被夸大了:

// Mock Ajax stuff:
var $ = {
  ajax: function(options) {
    // Simulate unknown return moment with setTimeout:
    setTimeout(function() {
      options.success(options.url + " is a great city...");
    }, Math.random() * 1500 + 50);
  }
}
function ViewModel() {
  var self = this;
  self.items = ko.observableArray([]);
  
  var places = ["london", "brussels", "paris"];
  
  function wikiInit(place, callback) {
    $.ajax({
      url: "http://en.wikipedia.org/w/" + place,
      success: function(result) {
        callback(result);
      }
    });
  }
  
  for (var i = 0; i < places.length; i++) {
    wikiInit(places[i], function(wikiString) {
      self.items.push(wikiString);
      console.log(self.items());
    });
  }
}
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<ul data-bind="foreach: items">
  <li data-bind="text: $data"></li>
</ul>

如您所见,每次运行时,结果都有可能出现故障。

选项 1 是将新项目拼接到数组中您希望它们所在的位置(而不是使用 push (。这是IMO有点复杂的方法,我将把代码留给读者。

选项 2,一种可能更简单的方法是事先准备数组,并在回调时填充数组。 例如像这样:

// Mock Ajax stuff:
var $ = {
  ajax: function(options) {
    // Simulate unknown return moment with setTimeout:
    setTimeout(function() {
      options.success(options.url + " is a great city...");
    }, Math.random() * 1500 + 50);
  }
}
function Place(data) {
  this.place = data.place;
  this.wikistuff = ko.observable("");
}
function ViewModel() {
  var self = this;
  self.items = ko.observableArray([]);
  
  var places = ["london", "brussels", "paris"];
  
  function wikiInit(place, callback) {
    var placeVm = new Place({ place: place });
    self.items.push(placeVm);
    $.ajax({
      url: "http://en.wikipedia.org/w/" + place,
      success: function(result) {
        callback(result, placeVm);
      }
    });
  }
  
  for (var i = 0; i < places.length; i++) {
    wikiInit(places[i], function(wikiString, placeVm) {
      placeVm.wikistuff(wikiString);
    });
  }
}
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<ul data-bind="foreach: items">
  <li data-bind="text: wikistuff"></li>
</ul>

此结果始终按原始数组的顺序排列。

它当前始终显示项目符号,甚至在回调完成之前。我这样做只是为了演示解决方案的工作原理,但你可以很容易地做到这一点......

<li data-bind="text: wikistuff, visible: !!wikistuff()"></li>

。如果需要。