即使禁用离线,AWS AppSync 查询也会返回缓存响应

Posted

技术标签:

【中文标题】即使禁用离线,AWS AppSync 查询也会返回缓存响应【英文标题】:AWS AppSync query returns cached response even when offline is disabled 【发布时间】:2018-12-20 18:18:03 【问题描述】:

我有一个使用 AWS AppSync 的相当简单的节点应用程序。我能够成功运行查询和突变,但我最近发现,如果我运行两次查询,我会得到相同的响应——即使我知道后端数据已经改变。在这种特殊情况下,查询由 lambda 支持,在深入研究时,我发现查询似乎没有在网络上发送出去,因为每次查询运行时都不会触发 lambda - 只是第一次.如果我使用控制台来模拟我的查询,那么一切都运行良好。如果我重新启动我的应用程序,那么第一次运行查询时它可以正常工作,但连续查询每次都返回相同的值。

这是我的代码的一部分:

  client.query(
    query: gql`
    query GetAbc($cId: String!) 
      getAbc(cId: $cId) 
        id
        name
        cs
      
    `,
    options: 
      fetchPolicy: 'no-cache'
    ,
    variables: 
      cid: event.cid
    
  )
    .then((data) => 
      // same data every time
    )

编辑:尝试network-only 等其他获取策略没有明显区别。

这是我设置客户端的方法,不是很干净,但似乎可以:

const makeAWSAppSyncClient = (credentials) => 
  return Promise.resolve(
    new AWSAppSyncClient(
      url: 'lalala',
      region: 'us-west-2',
      auth: 
        type: 'AWS_IAM',
        credentials: () => 
          return credentials
        
      ,
      disableOffline: true
    )
  )


getRemoteCredentials()
  .then((credentials) => 
    return makeAWSAppSyncClient(credentials)
  )
  .then((client) => 
    return client.hydrated()
  )
  .then((client) => 
    // client is good to use
  )

getRemoteCredentials 是一种将 IoT 身份验证转换为可与其他 AWS 开发工具包一起使用的普通 IAM 凭证的方法。这是有效的(因为如果不这样做,我就不会走得那么远)。

我的问题似乎与GraphQL Query Runs Sucessfully One Time and Fails To Run Again using Apollo and AWS AppSync 非常相似;我在节点环境中运行(而不是反应),但它本质上是相同的问题。


我认为这无关紧要,但为了完整起见,我应该提到我已经尝试过使用和不使用文档中的设置代码。这似乎没有什么区别(除了烦人的日志记录,见下文)但这里是:

global.WebSocket = require('ws')
global.window = global.window || 
  setTimeout: setTimeout,
  clearTimeout: clearTimeout,
  WebSocket: global.WebSocket,
  ArrayBuffer: global.ArrayBuffer,
  addEventListener: function ()  ,
  navigator:  onLine: true 


global.localStorage = 
  store: ,
  getItem: function (key) 
    return this.store[key]
  ,
  setItem: function (key, value) 
    this.store[key] = value
  ,
  removeItem: function (key) 
    delete this.store[key]
  
;
require('es6-promise').polyfill()
require('isomorphic-fetch')

摘自:https://docs.aws.amazon.com/appsync/latest/devguide/building-a-client-app-javascript.html

在客户端设置中使用此代码且没有offlineDisabled: true,我看到此行在控制台上不断喷涌:

redux-persist asyncLocalStorage 需要一个全局的 localStorage 对象。 要么使用不同的存储后端,要么这是一个通用的 redux 您可能应该有条件地坚持这样的应用程序: https://gist.github.com/rt2zz/ac9eb396793f95ff3c3b

然而,这对这个问题没有明显的影响。


更新:我的 package.json 依赖项,我在测试期间升级了这些,所以我的 yarn.lock 包含比此处列出的更新的版本。尽管如此:https://gist.github.com/macbutch/a319a2a7059adc3f68b9f9627598a8ca

更新 #2:我还从 CloudWatch 日志确认该查询只运行一次一次;我在计时器上定期运行一个突变,该计时器已成功调用并在 CloudWatch 中可见。这正如我所期望的那样工作,但查询不是。

更新 #3:我已经调试到 AppSync/Apollo 代码,可以看到我的 fetchPolicy 在apollo-client/core/QueryManager.js(我的 cmets)的这段代码中被更改为“缓存优先” :

QueryManager.prototype.fetchQuery = function (queryId, options, fetchType, fetchMoreForQueryId) 
    var _this = this;
    // Next line changes options.fetchPolicy to 'cache-first'
    var _a = options.variables, variables = _a === void 0 ?  : _a, _b = options.metadata, metadata = _b === void 0 ? null : _b, _c = options.fetchPolicy, fetchPolicy = _c === void 0 ? 'cache-first' : _c;
    var cache = this.dataStore.getCache();
    var query = cache.transformDocument(options.query);
    var storeResult;
    var needToFetch = fetchPolicy === 'network-only' || fetchPolicy === 'no-cache';
    // needToFetch is false (because fetchPolicy is 'cache-first')
    if (fetchType !== FetchType.refetch &&
        fetchPolicy !== 'network-only' &&
        fetchPolicy !== 'no-cache') 
        // so we come through this branch
        var _d = this.dataStore.getCache().diff(
            query: query,
            variables: variables,
            returnPartialData: true,
            optimistic: false,
        ), complete = _d.complete, result = _d.result;
        // here complete is true, result is from the cache
        needToFetch = !complete || fetchPolicy === 'cache-and-network';
        // needToFetch is still false
        storeResult = result;
    
    // skipping some stuff
    ...
    if (shouldFetch)  // shouldFetch is still false so this doesn't execute
        var networkResult = this.fetchRequest(
            requestId: requestId,
            queryId: queryId,
            document: query,
            options: options,
            fetchMoreForQueryId: fetchMoreForQueryId,
    
    // resolve with data from cache
    return Promise.resolve( data: storeResult );

如果我使用调试器将 shouldFetch 的值更改为 true,那么至少我看到网络请求发出并且我的 lambda 执行。我想我需要解开改变我的 fetchPolicy 正在做什么的那行。

【问题讨论】:

你为什么要覆盖本地存储?带有几个方法的对象 >我在节点环境中运行(而不是反应):这是否意味着您无权访问窗口对象? 我做或不做都没有区别。它在 AWS 示例中,所以我在那个 sn-p 中添加以表明我已经尝试过了,但它实际上是从文档中复制和粘贴的。 【参考方案1】:

在 AWS Lambda 函数中运行时,将查询 fetch-policy 设置为 'network-only'

我建议使用 WebSocketwindowlocalStorage 的覆盖,因为这些对象并不真正适用于 Lambda 函数。我通常在 Lambda 中用于 NodeJS 应用程序的设置如下所示。

'use strict';

// CONFIG
const AppSync = 
    "graphqlEndpoint": "...",
    "region": "...",
    "authenticationType": "...",
    // auth-specific keys
;

// POLYFILLS
global.WebSocket = require('ws');
global.window = global.window || 
    setTimeout: setTimeout,
    clearTimeout: clearTimeout,
    WebSocket: global.WebSocket,
    ArrayBuffer: global.ArrayBuffer,
    addEventListener: function ()  ,
    navigator:  onLine: true 
;
global.localStorage = 
    store: ,
    getItem: function (key) 
        return this.store[key]
    ,
    setItem: function (key, value) 
        this.store[key] = value
    ,
    removeItem: function (key) 
        delete this.store[key]
    
;
require('es6-promise').polyfill();
require('isomorphic-fetch');

// Require AppSync module
const AUTH_TYPE = require('aws-appsync/lib/link/auth-link').AUTH_TYPE;
const AWSAppSyncClient = require('aws-appsync').default;

// INIT
// Set up AppSync client
const client = new AWSAppSyncClient(
    url: AppSync.graphqlEndpoint,
    region: AppSync.region,
    auth: 
        type: AppSync.authenticationType,
        apiKey: AppSync.apiKey
    
);

【讨论】:

如果您继续遇到问题,您可以从您的 package.json 中粘贴依赖项和版本列表吗? 谢谢。是的,我已经尝试了各种获取策略,但这并没有什么区别。我不是作为 lambda 运行的——它是一个简单的节点应用程序。您在此处发布的设置看起来与我发布的非常相似。有什么具体的我应该看的吗? 关于依赖关系的好点。我会更新以包含它。 看看github.com/appwiz/colors/tree/master/node-listener。那是一个有效的 Node + AppSync 应用程序。 是的,好的,不过我已经做了很多工作了。我遇到的问题是,无论我运行多少次查询,我的代码都会收到第一次调用的结果(无论后端数据是否已更改)。事实上,AppSync 应该调用来获取数据的 lambda 只会被调用一次。【参考方案2】:

好的,我发现了问题。这是我的问题中代码的缩写版本:

 client.query(
    query: gql`...`,
    options: 
      fetchPolicy: 'no-cache'
    ,
    variables:  ... 
  )

这里更容易看出哪里出了问题。应该是这样的:

 client.query(
    query: gql`...`,
    fetchPolicy: 'network-only'
    variables:  ... 
  )

我原来的两个问题:

    fetchPolicy: 'no-cache' 似乎在这里不起作用(我得到一个空回复) 无需将fetchPolicy 放入options 对象中

graphql 客户端以不同的方式指定选项,我们在两者之间切换。

【讨论】:

【参考方案3】:

对于每个查询或/和初始化客户端,有两个选项可以使用 AppSyncClient/ApolloClient 启用/禁用缓存。

客户端配置:

client = new AWSAppSyncClient(
  
    url: 'https://myurl/graphql',
    region: 'my-aws-region',
    auth: 
      type: AUTH_TYPE.AWS_MY_AUTH_TYPE,
      credentials: await getMyAWSCredentialsOrToken()
    ,
    disableOffline: true
  ,
  
    cache: new InMemoryCache(),
    defaultOptions: 
      watchQuery: 
        fetchPolicy: 'no-cache', // <-- HERE: check the apollo fetch policy options
        errorPolicy: 'ignore'
      ,
      query: 
        fetchPolicy: 'no-cache',
        errorPolicy: 'all'
      
    
  
);

备选方案:查询选项:

export default graphql(gql`query  ... `, 
    options:  fetchPolicy: 'cache-and-network' ,
)(MyComponent);

有效的 fetchPolicy 值为:

cache-first:这是默认值,我们总是首先尝试从缓存中读取数据。如果完成查询所需的所有数据都在缓存中,则将返回该数据。如果缓存的结果不可用,Apollo 只会从网络中获取。此获取策略旨在最大限度地减少渲染组件时发送的网络请求数。 cache-and-network:此获取策略将让 Apollo 首先尝试从您的缓存中读取数据。如果完成查询所需的所有数据都在缓存中,则将返回该数据。但是,无论完整数据是否在您的缓存中,此 fetchPolicy 将始终使用网络接口执行查询,这与 cache-first 不同,后者仅在查询数据不在您的缓存中时才执行您的查询。此获取策略优化了用户获得快速响应,同时还尝试以额外的网络请求为代价使缓存数据与您的服务器数据保持一致。 network-only:此获取策略永远不会从缓存中返回初始数据。相反,它将始终使用您的网络接口向服务器发出请求。此获取策略优化了与服务器的数据一致性,但代价是在可用时立即响应用户。 仅缓存:此获取策略永远不会使用您的网络接口执行查询。相反,它总是会尝试从缓存中读取。如果缓存中不存在查询的数据,则会引发错误。此获取策略允许您仅与本地客户端缓存中的数据进行交互,而无需发出任何网络请求,从而使您的组件保持快速,但这意味着您的本地数据可能与服务器上的数据不一致。如果您只想与 Apollo 客户端缓存中的数据进行交互,请务必查看您的 ApolloClient 实例上可用的 readQuery()readFragment() 方法。 no-cache:此获取策略永远不会从缓存中返回您的初始数据。相反,它将始终使用您的网络接口向服务器发出请求。与仅网络策略不同,它也不会在查询完成后将任何数据写入缓存。

复制自:https://www.apollographql.com/docs/react/api/react-hoc/#graphql-options-for-queries

【讨论】:

以上是关于即使禁用离线,AWS AppSync 查询也会返回缓存响应的主要内容,如果未能解决你的问题,请参考以下文章

AWS AppSync Lambda 解析器获取查询返回类型

在 AWS AppSync 查询中返回嵌套 JSON

如何处理与 AWS AppSync 离线同步的数据?

aws appsync 离线:收到错误未提供变量 ID

是否可以使用 AWS AppSync 构建离线优先的移动应用程序?

为 AWS AppSync 客户端 iOS Swift 启用缓存