【问题标题】:Knockout JS - Promise recursionKnockout JS - 承诺递归
【发布时间】:2019-03-26 00:36:32
【问题描述】:

我有一种情况,我必须更新一个绑定到 HTML 元素的 Knockout observable,并且它的值正在通过异步操作(从服务器获取)进行更新。

我编写了以下示例代码:

const viewModel = function() {
  const self = this;

  self.fetchResults = function() {
    const id = ko.observable(0);
    fetch("https://jsonplaceholder.typicode.com/photos")
      .then(function(response) {
        return response.json();
      })
      .then(function(data) {
        id(data.length);
        console.log(id());
      })
    return id;
  };
};

ko.applyBindings(new viewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div data-bind="text: fetchResults()"></div>

它正在创建一个无限递归循环。

Knockout 是否因为值在异步函数内部更新而重复调用该函数?这个问题的根本原因是什么?

TIA!

编辑:

我在页面上有多个 HTML 元素请求绑定不同的信息,例如,

<div data-bind="text: fetchResults('some-url')"></div>
<div data-bind="text: fetchResults('some-different-url')"></div>
<div data-bind="text: fetchResults('another-url-altogether')"></div>

这意味着我需要在我所做的 fetchResults 函数中创建 observable(如果我的理解有误,请纠正我 :))

【问题讨论】:

    标签: javascript asynchronous recursion knockout.js promise


    【解决方案1】:

    问题在于,由于您使用的是表达式,因此该函数会在渲染时运行,并返回一个 observable。当 observable 的值发生变化时,它会再次渲染视图的该部分,这会再次调用函数,从而永远循环。

    在你说过的评论中:

    其实我需要在同一个页面中多次调用这个方法来获取不同的 fetch URI,并绑定到单独的 HTML 元素。

    ...并将此示例添加到问题中:

    <div data-bind="text: fetchResults('some-url')"></div>
    <div data-bind="text: fetchResults('some-different-url')"></div>
    <div data-bind="text: fetchResults('another-url-altogether')"></div>
    

    这确实改变了一些事情。我可能会使用自定义绑定而不是函数:

    ko.bindingHandlers.textFetch = {
      update(element, valueAccessor) {
        const url = ko.unwrap(valueAccessor());
        element.textContent = "0";
        fetch(url)
          .then((response) => {
            if (!response.ok) {
              throw new Error("HTTP error " + response.status);
            }
            return response.json();
          })
          .then((data) => {
            element.textContent = data.length;
          });
      }
    };
    const viewModel = function() {
    };
    
    ko.applyBindings(new viewModel());
    <div data-bind="textFetch: 'https://jsonplaceholder.typicode.com/photos'"></div>
    <div data-bind="textFetch: 'https://jsonplaceholder.typicode.com/posts'"></div>
    <div data-bind="textFetch: 'https://jsonplaceholder.typicode.com/comments'"></div>
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

    但如果你想让它与你当前的代码更相似,请为 url 保留一个 observables 的缓存:

    const viewModel = function() {
      const self = this;
      // In a modern environment, `observables` could be a Map rather than an object
      const observables = Object.create(null);
    
      self.fetchResults = function(url) {
        // Get the observable if we have it
        let id = observables[url];
        if (!id) {
          // We don't, create and remember one, do the fetch
          id = observables[url] = ko.observable(0);
          fetch(url)
            .then(function(response) {
              return response.json();
            })
            .then(function(data) {
              id(data.length);
            })
        }
        return id;
      };
    };
    
    ko.applyBindings(new viewModel());
    <div data-bind="text: fetchResults('https://jsonplaceholder.typicode.com/photos')"></div>
    <div data-bind="text: fetchResults('https://jsonplaceholder.typicode.com/posts')"></div>
    <div data-bind="text: fetchResults('https://jsonplaceholder.typicode.com/comments')"></div>
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

    旁注:您没有在 fetch 回调中检查成功。你不是唯一一个,我什至会说fetch API 设计不佳,因为我看到这支足球枪几乎每天都在 SO 上发射。我在我贫血的小博客上written it up,但基本上,你需要添加:

    if (!response.ok) {
      throw new Error("HTTP error " + response.status);
    }
    

    ...在您的履行处理程序中。

    【讨论】:

    • 其实我需要在同一个页面中针对不同的fetch URIs多次调用这个方法,并绑定到单独的HTML元素。更新相同的问题。
    • @Ozil - 啊,问题编辑确实改变了一些事情。查看更新的答案。
    • 感谢您的详细回复。但是,问题的根本原因是什么?非常感谢这里的任何帮助。
    • @Ozil - 我已经更新了我的想法,删除了第一个解决方案(因为它并不真正适用于您更新的问题),移动了第二个解决方案(绑定处理程序)到顶部,然后添加第三个解决方案,让您保持相同的text: fetchResults('url')。编码愉快!
    猜你喜欢
    • 1970-01-01
    • 2014-02-02
    • 2014-02-04
    • 1970-01-01
    • 2018-05-29
    • 2017-11-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-23
    相关资源
    最近更新 更多