MENU

前端的事务处理和任务队列

• March 28, 2023 • Read: 1505 • Web Program

事务处理 重要

事务处理解释

事务处理是一种将多个操作作为一个单独的、原子性的操作执行的机制。在事务处理中,多个操作被组合成一个单元,这个单元被视为单个不可分割的操作。这个单元中的所有操作都必须成功完成,否则整个事务将被回滚,回滚是指将所有操作撤销回到事务开始之前的状态。

事务处理通常应用于需要对多个数据进行修改的场景,如数据库管理系统中。在这种场景下,如果一个操作失败,所有已经完成的操作都必须撤销,以确保数据的一致性和完整性。

事务处理通常包括以下四个属性:

  1. 原子性(Atomicity):整个事务作为一个单独的、不可分割的操作执行,要么全部成功,要么全部失败。
  2. 一致性(Consistency):事务执行前和执行后,系统的状态应该保持一致。
  3. 隔离性(Isolation):并发的事务之间应该相互隔离,以防止数据损坏和冲突。
  4. 持久性(Durability):一旦事务被提交,它的结果应该是永久性的,即使系统发生故障也应该如此。

事务处理是一种重要的机制,它可以帮助保障数据的完整性和一致性,并且可以提高系统的可靠性和可用性。

在前端的应用场景

某个APP可能需要依次按照用户操作的特定顺序实现分步修改某些数据,假如步骤是3步,如果最后一步失败了,我需要吧第一步操作的结果进行回滚,这里就需要用到事务.

在 JavaScript 中,原生的异步函数和 Promise 不支持回滚(rollback)操作,因为这些操作都是异步执行的,无法像关系型数据库一样实现回滚操作。

但是,我们可以通过一些技巧来实现简单的回滚操作。例如,在执行事务之前,我们可以备份一些数据,以便在事务失败时恢复数据。如果事务成功,则删除备份数据。如果事务失败,则恢复备份数据以回滚事务。可以用到下面的代码实现事务处理和模拟简单的回滚:

function performTransaction(transactionFunctions) {
  let backupData = null;

  return new Promise((resolve, reject) => {
    const results = [];
    const transactionPromises = transactionFunctions.map(fn => fn());

    Promise.all(transactionPromises)
      .then(data => {
        results.push(...data);
        backupData = null; // 删除备份数据
        resolve(results);
      })
      .catch(error => {
        if (backupData) {
          // 恢复备份数据以回滚事务
          // 在实际情况下,你可能需要使用更安全的备份机制,如深拷贝或序列化/反序列化等
          console.log('Rolling back transaction...');
          // 恢复备份数据
        }
        reject(error);
      });
  });
}

function transaction1() {
  return new Promise((resolve, reject) => {
    // 备份数据
    if (!backupData) {
      backupData = 'backup';
    }
    setTimeout(() => {
      console.log('Transaction 1 completed');
      resolve(1);
    }, 1000);
  });
}

function transaction2() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('Transaction 2 completed');
      resolve(2);
    }, 2000);
  });
}

function transaction3() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('Transaction 3 failed');
      reject(new Error('Transaction failed'));
    }, 500);
  });
}

performTransaction([transaction1, transaction2, transaction3])
  .then(results => {
    console.log('All transactions completed successfully');
    console.log(results);
  })
  .catch(error => {
    console.error('Transaction failed', error);
  });

在这个例子中,我们定义了三个简单的事务函数,其中第一个函数会备份数据。如果事务成功,我们会删除备份数据。如果事务失败,则在 reject() 方法中检查是否存在备份数据,并根据需要恢复数据以回滚事务。

请注意,这个示例只是演示如何在 JavaScript 中实现简单的事务回滚操作。在实际项目中,你需要使用更严谨和可靠的备份机制来确保数据的完整性和一致性。


任务队列 重要

假如需求有三个弹窗。需要依次按照指定的顺序弹出来用户同意,必须严格按照顺序弹出哦.

解决方案 1

可以使用 Promise 链式调用来实现按照指定的顺序弹出弹窗,等待用户同意之后再继续弹出下一个弹窗的需求。具体实现如下:

function showPopup(message) {
  return new Promise((resolve, reject) => {
    const popup = document.createElement('div');
    popup.innerHTML = `
      <div>${message}</div>
      <button class="ok-btn">OK</button>
    `;
    document.body.appendChild(popup);
    const okButton = popup.querySelector('.ok-btn');
    okButton.addEventListener('click', () => {
      popup.remove();
      resolve();
    });
  });
}

showPopup('Popup 1')
  .then(() => showPopup('Popup 2'))
  .then(() => showPopup('Popup 3'))
  .then(() => {
    console.log('All popups have been shown and user agreed');
    // 执行用户同意之后的逻辑
  })
  .catch(error => {
    console.error(error);
  });

在这个例子中,我们定义了一个 showPopup 函数,它会在页面上创建一个弹窗,显示给定的消息,并等待用户点击“OK”按钮之后才会解析 Promise

我们使用 Promisethen() 方法将 showPopup 函数链接起来,按照指定的顺序依次弹出弹窗。最后一个 then() 方法中的回调函数表示所有弹窗都已经弹出并且用户同意了,可以继续执行后续逻辑。如果在任何一个弹窗中用户点击了“取消”或关闭按钮,将会触发 Promisereject() 方法,执行 catch() 方法中的错误处理逻辑。

注意,这个例子中只是演示如何依次按照指定顺序弹出弹窗,并等待用户同意之后再弹出下一个弹窗。在实际项目中,你需要根据具体的需求来实现更加复杂的弹窗逻辑。这样确实可以实现需求,但是不够优雅和灵活,最优解应该使用任务队列的方式来解决,实现代码如下:

function showPopup(message) {
  return new Promise((resolve, reject) => {
    const popup = document.createElement('div');
    popup.innerHTML = `
      <div>${message}</div>
      <button class="ok-btn">OK</button>
    `;
    document.body.appendChild(popup);
    const okButton = popup.querySelector('.ok-btn');
    okButton.addEventListener('click', () => {
      popup.remove();
      resolve();
    });
  });
}

const popupQueue = ['Popup 1', 'Popup 2', 'Popup 3'];
const showNextPopup = () => {
  const message = popupQueue.shift();
  if (message) {
    showPopup(message).then(() => {
      showNextPopup();
    });
  } else {
    console.log('All popups have been shown and user agreed');
    // 执行用户同意之后的逻辑
  }
};

showNextPopup();

实际上,使用队列是一种有效的方式来保证弹窗按照指定的顺序弹出,因为队列是一种先进先出(FIFO)的数据结构,它确保元素按照它们被添加的顺序处理。
因为浏览器的事件循环(event loop)的机制。当我们使用 Promise 解析来控制队列时,JavaScript 引擎会在事件循环的下一个迭代中执行每个 Promise,这意味着我们可以在 Promise 解析的回调函数中控制下一个 Promise 的执行。因此,使用 Promise 队列是一种可以保证顺序性的方法。

在这个例子中,我们使用 Promise 解析来控制弹窗的顺序。在 showNextPopup 函数中,我们首先从 popupQueue 中取出下一个弹窗的消息,然后使用 showPopup 函数创建一个弹窗,并在 Promise 解析的回调函数中调用 showNextPopup 函数来弹出下一个弹窗。这样,即使在多个 Promise 解析的情况下,我们仍然可以确保弹窗的顺序性。

Leave a Comment

5 Comments
  1. ?

  2. 很棒的讲解,学习了~@(吐舌)

    1. @铅笔Naruto@(呵呵)

  3. 写的通俗易懂,感谢整理!

    1. @磊磊落落@(花心)