MENU

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

• March 28, 2023 • Read: 765 • 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 解析的情况下,我们仍然可以确保弹窗的顺序性。

Archives QR Code
QR Code for this page
Tipping QR Code
Leave a Comment

5 Comments
  1. ?

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

    1. @铅笔Naruto@(呵呵)

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

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