如何在保持纯净的同时同时使用 Promises 和 Cursors?

How can I use Promises and Cursors together while remaining pure?

在我的 类 中,我一直在学习 indexeddb 以及如何处理异步性。为了扩展自己和学习更多知识,我一直在尝试在我的代码中使用函数式范例。

我以前一直在使用游标,但我意识到我的代码并非完全不可变或无状态,这让我很困扰。我想知道是否有一种方法可以使用游标而不必求助于将元素推入数组。

目前,我使用的是:

async function getTable(){
   return new Promise(function(resolve, reject){
      const db = await connect();
      const transaction = await db.transaction(["objectStore"], "readonly");
      const store = await transaction.objectStore("objectStore");
      var myArray = [];
      store.openCursor().onsuccess = function(evt) {                   
         var cursor = evt.target.result;
         if (cursor) {
            myArray.push(cursor.value);
            //I don't want to use push, because it's impure. See link:
            cursor.continue();
         } else {
            resolve(myArray);
      }
   }
}

//link: https://en.wikipedia.org/wiki/Purely_functional_programming

而且效果很好。但它并不纯粹,它使用的是push。如果有的话,我想学习另一种方法。

谢谢!

您可以本着函数式编程的精神做几件事,但在 JavaScript 中可能不值得。

例如,要实现不变性数组,至少在精神上,您只需在每次要向数组添加元素时创建并 return 一个新数组。我想如果我正确地记得我的 Scheme 函数被称为 cons.

function push(array, newValue) {
  const copy = copyArray(array);
  copy.push(newValue);
  return copy;
}

function copyArray(array) {
  const copy = [];
  for(const oldValue of array) {
    copy.push(oldValue);
  }
  return copy;
}

// Fancy spread operator syntax implementation if you are so inclined
function copyArray2(inputArray) {
  return [...inputArray];
}

现在不是改变输入数组,而是创建它的修改副本。请记住,这绝对是可怕的性能,您可能永远不想在真正的应用程序中这样做。

您可以更进一步,并使用一些基于堆栈的方法。同样,这非常糟糕,但它基本上可以创建一个 return 函数的推送函数。当您添加项目时,堆栈的大小会增加,然后当您展开它时,它会展开成一个值数组。

我的第二点是,您可以通过使用 indexedDB 的更新的、文档较少的功能来完全避免这种数组构建。具体来说,IDBObjectStore.prototype.getAll。这个函数会为你创建数组,不透明的,如果它是不透明的,你永远不会知道隐藏在它的抽象中的任何 FP 反模式,因此没有违反规则。

function getTable(){
  return new Promise(function(resolve, reject){
    const db = await connect();
    const transaction = await db.transaction(["objectStore"], "readonly");
    const store = await transaction.objectStore("objectStore");
    const request = store.getAll();
    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
 }
}

我的第三点很简单,用db.transaction("objectStore", "readonly");代替db.transaction(["objectStore"], "readonly");。数组参数是可选的,最好保持简单。

我第四点很简单,用db.transaction("objectStore");代替db.transaction(["objectStore"], "readonly");。 "readonly"是交易的默认模式,不需要指定。通过不指定参数,您的代码的目的已经足够清楚地传达出来,省略参数也不会那么冗长。

第五点是您在函数定义中使用了 async 说明符 (?)。你不需要在这里使用它。您有一个同步函数 returning 一个 Promise 的实例。如果有的话,指定 async 会导致您对代码的作用更加困惑。相反,您可能希望在 使用 函数时使用异步限定符。

我的第六点是你在调用connect()时违反了一些函数式编程原则。 connect 连接到什么?隐含的全局状态。这相当违背函数式编程的精神。因此,您的连接参数 必须 是函数本身的参数,这样您就不会依赖于使用哪个数据库的隐含知识。

我的第七点是你在使用数据库。函数式程序员在数据库、任何 I/O 或与外界的交互方面有太多问题,他们似乎喜欢假装没有这些问题。因此,如果您想使用函数式方法,您可能根本不应该使用数据库。

我的八点是在 promise 内连接(调用和等待 connect)绝对是一种反模式。目标是链接承诺,以便一个接一个地开始。调用者必须调用 connect 并且 then 调用 getTable,或者 getTable 必须调用 connect 然后完成其余的承诺工作。

我的第九点是我什至不确定这是如何执行的。您传递给 Promise 构造函数的执行器函数没有限定为 async。因此,您在非限定函数中使用了 await 修饰符。这应该会引发错误。从技术上讲,一个 promise 吞噬了异常,这意味着这个 promise 应该总是被拒绝。

我的第十点是你到处都在使用 async。我不知道发生了什么,除非你的 connect 函数是 return 某种包装器库,但对 IDBDatabase.prototype.transactionIDBTransaction.prototype.objectStore 的调用是同步的。你为什么要等他们是没有意义的。他们不return承诺。'

我的第十一点是你没有注意错误。 request.onsuccess 出错时不回调。这可能会导致您的承诺永远无法兑现。您还需要考虑失败案例。

我的第十二点是您的 onsuccess 处理函数似乎缺少右括号。我不确定这段代码是如何被成功解释的。