Javascript 无RxJS的异步流控制

Javascript 无RxJS的异步流控制,javascript,promise,observable,Javascript,Promise,Observable,我正在开发一个移动应用程序,我希望它先离线。 假设我想显示“公司”页面,所以我请求公司数据 下面是我要做的,我在本地存储中查找公司数据(在我的例子中是indexedDB),同时调用服务器获取相同的数据(以获取潜在的更新)。在每次回调中,我都会用获取的数据更新视图。首先从本地数据更新,然后从远程数据更新 为了避免竞争条件,我在本地存储回调中签入了一个名为“remoteDataAlreadyFetched”的布尔值(以防远程数据在存储响应之前到达) 我的问题是:我应该如何处理?我的视图控制器中有两个

我正在开发一个移动应用程序,我希望它先离线。 假设我想显示“公司”页面,所以我请求公司数据

下面是我要做的,我在本地存储中查找公司数据(在我的例子中是indexedDB),同时调用服务器获取相同的数据(以获取潜在的更新)。在每次回调中,我都会用获取的数据更新视图。首先从本地数据更新,然后从远程数据更新

为了避免竞争条件,我在本地存储回调中签入了一个名为“remoteDataAlreadyFetched”的布尔值(以防远程数据在存储响应之前到达)

我的问题是:我应该如何处理?我的视图控制器中有两个单独的承诺(一个用于本地,一个用于远程)?我应该使用可观察的吗?这看起来有些过分,因为响应不会超过2个(本地和远程)。我错过什么了吗

非常感谢你的帮助

编辑:

这就是我使用RxJS的方式。不知道在这种情况下使用可见光是否是一种不好的做法

public static getOfflineFirstObservable(localCall, remoteCall): Observable<any> {
  const data$ = new BehaviorSubject(null);
  var hasFetchedRemoteData = false;

  localCall().then(localDatas => {
    if (!hasFetchedRemoteData) data$.next(localDatas);
  });

  remoteCall().then(remoteDatas => {
    data$.next(remoteDatas);
    hasFetchedRemoteData = true;
  });

  return data$.asObservable();
}
公共静态getOfflineFirstObservable(localCall,remoteCall):可观察{ const data$=新行为主体(null); var hasFetchedRemoteData=false; localCall()。然后(localDatas=>{ 如果(!hasFetchedRemoteData)数据$.next(localDatas); }); remoteCall()。然后(remoteDatas=>{ 数据$.next(远程数据); hasFetchedRemoteData=true; }); 返回数据$.asObservable(); }
这是一个非常有趣的问题,因此您正在尝试确定两个异步操作的顺序

我实现了一些可以实现这一点的东西,请检查下面的代码,或者直接运行它们;)


如果您处于脱机状态,那么尝试获取远程数据将导致拒绝承诺(使用promise.race)。如果您要做的只是首先从缓存中获取项(如果它在缓存中),而不是尝试将其远程获取,则可以执行以下操作:

const cacheBuilder = promises => fetcher => setFn => getFn => url => {
  //using url as key but can be url+JSON.stringify(parameters)+method(GET/POST)
  const key = url;
  //get from cache first if exist
  return getFn(key).catch(()=>{
    if(promises[key]){
      return promises[key];//return active promise
    }
    promises[key]=fetcher(url);
    return promises[key];

  })
  .then(
    result=>{
      if(!promises[key]){//update cache, this will cause requests to server
        fetcher(url).then(result=>setFn(key,result)).catch(ignore=>ignore);
      }
      promises[key]=undefined;
      setFn(key,result);
      return result;
    }
  );
}

const cacheFirst = cacheBuilder(
  {}//store active promises here
)(
  //fetch function (can be $.get or something else)
  //  I am only using url here but you could use (url,params,method,headers) as well
  url=>
    //remove "console.log ||" it's to show that multiple active fetches share promises
    //  asking for fetch("/") multiple times while first time is not resolved
    //  will not cause multiple requests
    console.log("fetching:",url) ||
    fetch(url)
    .then(response=>response.text())
    .then(result=>result.substr(0,10))
)(
  //how to set an item in local storage
  (key,value)=>{
    newStorage = JSON.parse(localStorage.getItem("netCache")||"{}");
    newStorage[key]=value;
    localStorage.setItem("netCache",JSON.stringify(newStorage));
  }
)(
  //how to get an item based on key (can be url or url + JSON.stringify(parameters) or url+params+method...)
  key=>
    Promise.resolve(
      JSON.parse(localStorage.getItem("netCache")||"{}")[key] ||
      Promise.reject("Not in cache")
    )
);

Promise.all([//should not cause multiple requests, should have only one request made
  cacheFirst("/"),
  cacheFirst("/"),
  cacheFirst("/"),
  cacheFirst("/")
]).then(
  ()=>cacheFirst("/")//should come from cache as well, no request made
)
下面是一个示例,其中所有实现都在一个函数中,而不传递fetch、getter和setter:

const cacheFirst = (promises => url => {
  //using url as key but can be url+JSON.stringify(parameters)+method(GET/POST)
  const key = url;
  const fetcher = url=>
    fetch(url)
    .then(response=>response.text())
    .then(result=>result.substr(0,10));
  const setFn = (key,value)=>{
    newStorage = JSON.parse(localStorage.getItem("netCache")||"{}");
    newStorage[key]=value;
    localStorage.setItem("netCache",JSON.stringify(newStorage));
  }
  const getFn = key=>
    Promise.resolve(
      JSON.parse(localStorage.getItem("netCache")||"{}")[key] ||
      Promise.reject("Not in cache")
    );
  //get from cache first if exist
  return getFn(key).catch(()=>{
    if(promises[key]){
      return promises[key];//return active promise
    }
    promises[key]=fetcher(url);
    return promises[key];
  })
  .then(
    result=>{
      //update cache if result didn't came from request, this will cause requests to server
      if(!promises[key]){
        fetcher(url)
        .then(result=>setFn(key,result))
        .catch(ignore=>ignore);
      }
      promises[key]=undefined;
      setFn(key,result);
      return result;
    }
  );
})({})//IIFE passing in the promises object to store active promises

我最终决定创建两个单独的承诺。我在页面加载时获取本地数据(如果存在),并立即尝试获取远程数据。因此,用户可以立即看到内容,但仍然可以在右上角看到一个微调器,指示用户的远程数据已被请求。当请求返回时,视图将更新

我之所以决定这样做,是因为页面也有刷新功能,所以将两个调用(本地和远程)分开似乎更明智,以便能够根据需要多次调用服务器

谢谢你的回答。
@HMR您的解决方案非常有趣,但并不真正符合我的需要。非常感谢

很有趣。问题是localFirst不可能赢得比赛,对吗?那么为什么要使用Promise.race()?同样的想法是在我的提供者中编写这个逻辑,这样我就可以在我的视图控制器中有一个干净的调用。在这种情况下,看起来我必须传递一个回调函数,并使用返回的承诺来处理“脱机优先”行为,对吗?不知何故,我错了,不管我的答案是什么,我认为当本地先到达然后远程返回时,您必须使用两个处理程序,这也让我想起了Rxjs:)尽管我不太喜欢Rxjs。如果您可以接受应该有两个处理程序,那么我认为我们可以在不使用RxjsI的情况下解决这个问题,但在这里可能没有帮助:(,但我认为这个问题应该概括为一些问题,比如“没有rxjs的异步流控制”,从更具建设性的角度来看,这可能会有更好的解决方案。Arf,好的。我的一个朋友告诉我可以使用服务工人,但iOS支持还没有。好的,我将这个问题重命名为thx Octave:)我想做的是让我的应用程序尽快出现。要做到这一点,我想在远程呼叫期间立即向用户显示缓存的数据。如果用户脱机,则远程呼叫将不会返回,但一切正常。如果远程呼叫返回,则我想更新缓存和显示的数据。感谢您的回答,我将进行一次数据删除eper查看一下我一遍又一遍地阅读你的代码,老实说,我在理解它时遇到了一些困难^^"这是下一个级别的sh*t。这是函数式编程吗?@Maslow更新了代码,以便在资源从缓存中发出时再次请求资源并更新缓存。如果请求资源时出错,则会忽略错误并不会更新缓存。您可以编写代码,而不是传入所有函数,如fetcher、getter和settert也进入函数中。好吧,这更容易阅读。所以你存储所有运行的承诺以避免多次进行相同的调用…setFn只是在LS中设置一个值,好吧…没有在fetcher函数中获得substr(0,10)…最后你的承诺只返回一次,对吗?我不会有我的两步显示(立即缓存数据…然后在查询返回时提取数据)。这段代码是避免进行相同调用的一种方法。这很酷,但不能解决我的问题think@Maslow若数据在缓存中不可用,它将尝试从网络中获取,若数据在缓存中,则返回该数据,并使用网络数据更新缓存(检查注释:
//如果结果没有出现,则更新缓存
)。subsr只是为了让您可以在控制台中运行它,并且没有非常大的字符串,我正在使用,但是注释表明您可以在这里使用jQuery或任何东西,它只是一个发出网络请求并返回结果的函数。
const cacheFirst = (promises => url => {
  //using url as key but can be url+JSON.stringify(parameters)+method(GET/POST)
  const key = url;
  const fetcher = url=>
    fetch(url)
    .then(response=>response.text())
    .then(result=>result.substr(0,10));
  const setFn = (key,value)=>{
    newStorage = JSON.parse(localStorage.getItem("netCache")||"{}");
    newStorage[key]=value;
    localStorage.setItem("netCache",JSON.stringify(newStorage));
  }
  const getFn = key=>
    Promise.resolve(
      JSON.parse(localStorage.getItem("netCache")||"{}")[key] ||
      Promise.reject("Not in cache")
    );
  //get from cache first if exist
  return getFn(key).catch(()=>{
    if(promises[key]){
      return promises[key];//return active promise
    }
    promises[key]=fetcher(url);
    return promises[key];
  })
  .then(
    result=>{
      //update cache if result didn't came from request, this will cause requests to server
      if(!promises[key]){
        fetcher(url)
        .then(result=>setFn(key,result))
        .catch(ignore=>ignore);
      }
      promises[key]=undefined;
      setFn(key,result);
      return result;
    }
  );
})({})//IIFE passing in the promises object to store active promises