没有RxJS的异步流控制

时间:2018-04-10 09:40:59

标签: javascript promise observable

我正在使用移动应用,我希望它首先离线。 让我们说我想展示"公司"页面,所以我要求公司数据。

这是我的工作,我在本地存储中寻找公司数据(在我的情况下是indexedDB),同时我为服务器调用相同的数据(以获得潜在的更新)。在每个回调中,我使用获取的数据更新视图。首先从本地数据更新然后从远程数据更新。

为避免竞争条件,我有一个名为" remoteDataAlreadyFetched"的布尔值。我检查我的本地存储回调(如果远程数据在存储响应之前到达)

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

非常感谢你的帮助

编辑:

这就是我用RxJS做的方法。不知道在这种情况下使用Observables是不好的做法......

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();
}

3 个答案:

答案 0 :(得分:1)

非常有趣的问题,因此您正在尝试确保两个异步操作的顺序。

我实现了一些能够实现这一点的东西,检查下面的代码或运行它们;)

// two promise with no guaranteed order:
var localPromise = new Promise(resolve => setTimeout(resolve, Math.random() * 1000))
var remotePromise = new Promise(resolve => setTimeout(resolve, Math.random() * 1000))

// wrap them
var localFirst = Promise.resolve(localPromise)
    // can also add your handler for localdata
    .then(() => Promise.resolve(remotePromise))
var remoteFirst = Promise.resolve(remotePromise)

// viola
Promise.race([localFirst, remoteFirst]).then(console.log)

答案 1 :(得分:1)

如果您处于离线状态,那么尝试获取远程数据将导致拒绝承诺(使用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

答案 2 :(得分:0)

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

我已经决定这样做,因为该页面还有一个刷新功能,因此将2个调用(本地和远程)分开以便能够根据需要多次调用服务器似乎更聪明。 / p>

感谢您的回答。 @HMR您的解决方案非常有趣,但并不能满足我的需求。非常感谢