Pouchdb 分页

Pouchdb pagination

我正在寻找一种通过指定我想要的页码在 pouchdb 中分页的方法。

我遇到的最接近的例子是:

var options = {limit : 5};
function fetchNextPage() {
  pouch.allDocs(options, function (err, response) {
    if (response && response.rows.length > 0) {
      options.startkey = response.rows[response.rows.length - 1].id;
      options.skip = 1;
    }
  });
}

然而,它假设您正在一页接一页地分页并连续多次调用它。

我需要的是一种通过单个查询检索第 5 页的方法。

这个问题没有简单的答案。 3.2.5.6. Jump to Page

的一小部分

One drawback of the linked list style pagination is that you can’t pre-compute the rows for a particular page from the page number and the rows per page. Jumping to a specific page doesn’t really work. Our gut reaction, if that concern is raised, is, “Not even Google is doing that!” and we tend to get away with it. Google always pretends on the first page to find 10 more pages of results. Only if you click on the second page (something very few people actually do) might Google display a reduced set of pages. If you page through the results, you get links for the previous and next 10 pages, but no more. Pre-computing the necessary startkey and startkey_docid for 20 pages is a feasible operation and a pragmatic optimization to know the rows for every page in a result set that is potentially tens of thousands of rows long, or more.

如果幸运的话,每个文档都有一个有序的序号,那么可以构建一个视图来轻松导航页面。

另一种策略是预先计算(预加载)一系列键,这种方法更合理但比较复杂。下面的代码片段创建了一个简单的数据库,可以通过导航链接进行分页。

每页有 5 个文档,每个“章节”有 10 页。 computePages 执行前瞻

// look ahead and cache startkey for pages.
async function computePages(startPage, perPage, lookAheadPages, startKey) {
  let options = {
    limit: perPage * lookAheadPages,
    include_docs: false,
    reduce: false
  };
  // adjust. This happens when a requested page has no key cached.
  if (startKey !== undefined) {
    options.startkey = startKey;
    options.skip = perPage; // not ideal, but tolerable probably?
  }
  const result = await db.allDocs(options);
  // use max to prevent result overrun
  // only the first key of each page is stored
  const max = Math.min(options.limit, result.rows.length)
  for (let i = 0; i < max; i += perPage) {
    page_keys[startPage++] = result.rows[i].id;
  }
}

page_keys提供了一个key/value存储映射页码到开始键。通常 skip 的 1 之外的任何其他内容都是危险信号,但这在这里是合理的 - 我们不会跳过 100 个文档,对吗?

我只是把它放在一起,所以它不完美并且可能有错误,但它确实展示了一般的页面导航。

function gel(id) {
  return document.getElementById(id);
}

// canned test documents
function getDocsToInstall() {
  let docs = [];
  // doc ids are a silly sequence of characters.
  for (let i = 33; i < 255; i++) {
    docs.push({
      _id: `doc-${String.fromCharCode(i)}`
    });
  }
  return docs;
}

// init db instance
let db;
async function initDb() {
  db = new PouchDB('test', {
    adapter: 'memory'
  });
  await db.bulkDocs(getDocsToInstall());
}

// documents to show per page
const rows_per_page = 5;
// how many pages to preload into the page_keys list.
const look_ahead_pages = 10;
// page key cache: key = page number, value = document key
const page_keys = {};
// the current page being viewed
let page_keys_index = 0;
// track total rows available to prevent rendering links beyond available pages.
let total_rows = undefined;
async function showPage(page) {
  // load the docs for this page
  let options = {
    limit: rows_per_page,
    include_docs: true,
    startkey: page_keys[page] // page index is computed
  };
  let result = await db.allDocs(options);
  // see renderNav. Here, there is NO accounting for live changes to the db.
  total_rows = total_rows || result.total_rows;
  // just display the doc ids.
  const view = gel('view');
  view.innerText = result.rows.map(row => row.id).join("\n");
}

// look ahead and cache startkey for pages.
async function computePages(startPage, perPage, lookAheadPages, startKey) {
  let options = {
    limit: perPage * lookAheadPages,
    include_docs: false,
    reduce: false
  };
  // adjust. This happens when a requested page has no key cached.
  if (startKey !== undefined) {
    options.startkey = startKey;
    options.skip = perPage; // not ideal, but tolerable probably?
  }
  const result = await db.allDocs(options);
  // use max to prevent result overrun
  // only the first key of each page is stored
  const max = Math.min(options.limit, result.rows.length)
  for (let i = 0; i < max; i += perPage) {
    page_keys[startPage++] = result.rows[i].id;
  }
}


// show page links and optional skip backward/forward links.
let last_chapter;
async function renderNav() {
  // calculate which page to start linking.
  const chapter = Math.floor(page_keys_index / look_ahead_pages);
  if (chapter !== last_chapter) {
    last_chapter = chapter;
    const start = chapter * look_ahead_pages;
    let html = "";
    // don't render more page links than possible.
    let max = Math.min(start + look_ahead_pages, total_rows / rows_per_page);

    // render prev link if nav'ed past 1st chapter.
    if (start > 0) {
      html = `<a href="javascript:void" onclick="navTo(${start-1})">&lt;</a>&nbsp;&nbsp;`;
    }

    for (let i = start; i < max; i++) {
      html += `<a href="javascript:void" onclick="navTo(${i})">${i+1}</a>&nbsp;`;
    }
    // if more pages available, render the 'next' link
    if (max % look_ahead_pages === 0) {
      html += `&nbsp;<a href="javascript:void" onclick="navTo(${start+look_ahead_pages})">&gt;</a>&nbsp;`;
    }

    gel("nav").innerHTML = html;
  }
}

async function navTo(page) {
  if (page_keys[page] === undefined) {
    // page key not cached - compute more page keys.
    await computePages(page, rows_per_page, look_ahead_pages, page_keys[page - 1]);
  }
  page_keys_index = page;
  await showPage(page_keys_index);
  renderNav();

}

initDb().then(async() => {
  await navTo(0);
});
<script src="https://cdn.jsdelivr.net/npm/pouchdb@7.1.1/dist/pouchdb.min.js"></script>
<script src="https://github.com/pouchdb/pouchdb/releases/download/7.1.1/pouchdb.memory.min.js"></script>
<pre id="view"></pre>
<hr/>
<div id="nav">
  </nav>