首页 > 解决方案 > 淘汰赛 JS - 承诺递归

问题描述

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

我编写了以下示例代码:

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 是否因为异步函数内部正在更新值而重复调用该函数?这个问题的根本原因可能是什么?

蒂亚!

编辑:

我在页面上有多个 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(如果我的理解有误,请纠正我 :))

标签: javascriptasynchronousrecursionknockout.jspromise

解决方案


The problem is that since you're using an expression, the function gets run on render, returning an observable. When the observable's value changes, it makes that part of the view render again, which calls the function again, which cycles forever.

In a comment you've said:

Actually I need to call this method multiple times for different fetch URIs in the same page, and bind to separate HTML elements.

...and added this example to the question:

<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>

That does change things a bit. I'd probably use a custom binding rather than a function:

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>


But if you want to keep it more similar to your current code, keep a cache of observables for urls:

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>


Side note: You're not checking for success in your fetch callback. You're not the only one, I'd go so far as to say the fetch API is poorly-designed as I see this footgun being fired just about every day here on SO. I've written it up on my anemic little blog, but basically, you need to add:

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

...in your fulfillment handler.


推荐阅读