【带并发限制的异步调度器Ⅱ】点餐排队系统Demo

要点:

  1. 店家可以设置任意张数的N人桌,根据自家容客量来设置,并且初始化时会给每张桌子一个编号。
    初始化桌子的数目以及能使用的人数:
    2人桌:3张, 4人桌:9张, 6人桌:6张, 8人桌:4张
const deskSettings = [
  { peopleNum: 2, maxExecutingNum: 3 },
  { peopleNum: 4, maxExecutingNum: 9 },
  { peopleNum: 6, maxExecutingNum: 6 },
  { peopleNum: 8, maxExecutingNum: 4 },
];
const scheduler = new Scheduler(deskSettings);

Scheduler内部初始化,根据N人桌来构造相应的任务管理器,因此会构造4个管理器(2人,4人,6人,8人),这4个管理器会放在taskMapper映射表中。当需要取用时,会从映射表中拿出来

//peopleNum表示几人桌,maxTaskNum表示这家店有多少个这样的桌子
const letterStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
class Scheduler {
  constructor(TaskSettings) {
    if ((TaskSettings||[]).length > 0) {
      this.taskMapper = {};
      for (const item of TaskSettings) {
        let { peopleNum, maxExecutingNum } = item;
        if (peopleNum <= 0) {
          continue;
        }
        let taskObj = {
          tasks: [],
          executingTasks: [],
          maxTaskNum: maxExecutingNum,
          idList: []
        };

        let letter = letterStr.substr(peopleNum-1,1);
        for (let i = 1; i <= maxExecutingNum; i++) {
          let idStr = letter + (i.toString().length<2 ? '0'+i : i);
          taskObj.idList.push(idStr);
        }
        this.taskMapper['peopleNum_'+peopleNum] = taskObj;
      }
    } else {
      this.tasks = []; //待执行的任务列表
      this.executingTasks = []; //正在执行的任务列表
      this.maxTaskNum = maxTaskNum;  //最大执行任务数
    }
  }
}

构造出来的taskMapper结构如下:tasks,待执行的任务列表;executingTasks,正在执行的任务列表,maxTaskNum,最大执行任务数,
idList,所有桌子的编号

{
  peopleNum_2: {
    tasks: [],
    executingTasks: [],
    maxTaskNum: 3,
    idList: [ 'B01', 'B02', 'B03' ]
  },
  peopleNum_4: {
    tasks: [],
    executingTasks: [],
    maxTaskNum: 9,
    idList: ['D01', 'D02', 'D03', 'D04', 'D05', 'D06', 'D07', 'D08', 'D09' ]
  },
  peopleNum_6: {
    tasks: [],
    executingTasks: [],
    maxTaskNum: 6,
    idList: [ 'F01', 'F02', 'F03', 'F04', 'F05', 'F06' ]
  },
  peopleNum_8: {
    tasks: [],
    executingTasks: [],
    maxTaskNum: 4,
    idList: [ 'H01', 'H02', 'H03', 'H04' ]
  }
}
  1. 由于每桌人的用餐时长不一样,因此为了模拟真实情况,会随机设定顾客的用餐时长:
//顾客随机用餐的时长:秒
const dinnerTimeArr = [10,20,30,40];
//从数组中随机取出元素,N不能大于数组的长度
function getRandomArrayElements(arr, N) {
  let indexArray = [];
  arr = arr || [];
  N = N || 0;
  if (arr.length === 0) {
    return [];
  }
  let max = arr.length - 1;
  let min = 0;
  for (let i = 0; i < N; i++) {
    let randomIndex = parseInt(Math.random() * (max - min + 1)) + min; //取随机数组索引值
    while (indexArray.includes(randomIndex)) {
      randomIndex = parseInt(Math.random() * (max - min + 1)) + min;
    }
    indexArray.push(randomIndex);
  }

  let randomArray = indexArray.map(index => arr[index]);
  return randomArray;
}

以及用eatDinnerPromise来表示顾客的用餐行为:

function eatDinnerPromise(peopleNum, deskNum) {
  return new Promise((resolve, reject) => {
    let dinnerTime = (getRandomArrayElements(dinnerTimeArr,1))[0];
    console.log(`我们是${peopleNum}人桌,桌子编号【${deskNum}】,【${moment().format('HH:mm:ss')}】开始用餐,将会进行${dinnerTime}秒。`);
    let ts = dinnerTime*1000;
    setTimeout(()=> {
      console.log(`我们用餐结束【${moment().format('HH:mm:ss')}】,桌子编号【${deskNum}】,用时:${dinnerTime}秒`);
      resolve();
    },ts);
  });
}
  1. 店家的桌子数据初始化完毕,顾客用餐行为也准备完毕,接下来就需要把这些代码串联起来。首先来了一桌顾客,先把他们加入系统,Scheduler中应有一个add方法,
add(promiseMaker, peopleNum) {
    let task = this.generateTask(promiseMaker, peopleNum);
    let taskObj = this.taskMapper['peopleNum_'+peopleNum];

    if (taskObj.executingTasks.length < taskObj.maxTaskNum) {
      this.run(task, peopleNum);
    } else {
      taskObj.tasks.push(task);
    }

    return task.id;
  }

而由于外部加进来的promise不一定符合我们的数据结构需求,就需要用内部方法generateTask来转换一下,

generateTask(promiseMaker, peopleNum){
    let taskObj = this.taskMapper['peopleNum_'+peopleNum];
    //从生成的数据中随机拿一个id编号出来
    let taskId = this.getTaskId(taskObj.idList);

    return {
      id: taskId,
      peopleNum: peopleNum,
      promise: promiseMaker
    };
  }

当需要从N人桌的编号池中拿一个编号出来时,会需要用到getTaskId方法,为了模拟真实情况,从编号池中拿取是采用的随机拿取,并且会暂时的从编号池移除此编号,当用餐结束再把编号归还。

getTaskId(idList){
    let taskId = (getRandomArrayElements(idList, 1))[0];
    idList.splice(idList.findIndex(id => id === taskId), 1);

    return taskId;
  }

以上讲的只是如何把外面promise封装成内部需要的任务结构,接下来就是运行任务,当正在执行的任务列表有空余时,会直接把任务加入此列表运行,使用的是run方法

run(task, peopleNum) {
    let taskObj = this.taskMapper['peopleNum_'+peopleNum];
    let arrayLength = taskObj.executingTasks.push(task);
    let index = arrayLength - 1;
    task.promise(peopleNum, task.id).then(() => {
      taskObj.executingTasks.splice(index, 1);
      //归还id编号
      taskObj.idList.push(task.id);
      if (taskObj.tasks.length > 0) {
        let newTask = taskObj.tasks.shift();
        newTask.id = this.getTaskId(taskObj.idList);
        this.run(newTask, newTask.peopleNum);
      }
    })
  }

这个任务结束之后,会做3件事:
(1)从正在执行的任务列表executingTasks中把自己移除
(2)归还任务(桌子)编号
(3)若待执行任务列表tasks中有任务,会从头部取出一个新任务,并从之前移除任务的编号池里拿出一个编号给新任务运行

  1. 为了了解排在自己前面还有多少桌,会用show来展示排队信息:
//获取现在排在前面的桌子数目
  show(peopleNum,deskId){
    let taskObj = this.taskMapper['people_Num'+peopleNum];
    let index = taskObj.tasks.findIndex(item => item.id === deskId);
    let executingTaskNum = taskObj.executingTasks.length;
    console.log(`您的${peopleNum}人桌编号为${deskId},有${executingTaskNum}在吃`);
    console.log(`排在您前面的还有${index}桌`);

    return {
      preTaskNum: index,
      executingTaskNum: executingTaskNum
    };
  }

以上就把Scheduler构造完毕了,接着用一些例子来测试下成果:

scheduler.add(eatDinnerPromise,2);
scheduler.add(eatDinnerPromise,2);
scheduler.add(eatDinnerPromise,2);
scheduler.add(eatDinnerPromise,2);
scheduler.add(eatDinnerPromise,2);

打印出来的结果:

我们是2人桌,桌子编号【B02】,【23:27:39】开始用餐,将会进行20秒。
我们是2人桌,桌子编号【B03】,【23:27:39】开始用餐,将会进行30秒。
我们是2人桌,桌子编号【B01】,【23:27:39】开始用餐,将会进行20秒。
我们用餐结束【23:27:59】,桌子编号【B02】,用时:20秒
我们是2人桌,桌子编号【B02】,【23:27:59】开始用餐,将会进行40秒。
我们用餐结束【23:27:59】,桌子编号【B01】,用时:20秒
我们是2人桌,桌子编号【B01】,【23:27:59】开始用餐,将会进行20秒。
我们用餐结束【23:28:09】,桌子编号【B03】,用时:30秒
我们用餐结束【23:28:19】,桌子编号【B01】,用时:20秒

完整的代码链接:点餐排队系统Demo
结语:
根据【带并发限制的异步调度器】可以做出一个能用于实际场景的Demo例子,这就是举一反三的实际运用。
当然Demo肯定不能用于实际的,只能是演示可行性,若真正需要投入实际场景使用,还需要更多的扩展,如:操作界面可视化,配置数据持久化,手动选择用餐桌子,打印排队信息,统计日均用餐数等。
如果练手这个项目,我会采用Egg+React来实践,到时候做出来会分享在github上,敬请期待。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容