使用AJAX回调更新knockout observableArray会产生随机排序的结果

时间:2016-02-12 02:50:13

标签: javascript jquery ajax knockout.js

以下是一些复制问题的代码

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()的结果。我将API结果分别添加到不同的<div>,因此我需要将结果的索引与其各自条目的索引相同[]

1 个答案:

答案 0 :(得分:1)

订购时遇到两个问题:

  • 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 splice new items into the array所在位置(而非使用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>

......如果需要的话。