Child Process Trong Nodejs Là Gì? Khi Nào Thì Cần Sử Dụng

Trong quá trình làm việc với nodeJS, bạn sẽ gặp 1 vấn đề khi gọi tới một hàm xử lý tính toán tốn nhiều thời gian. Ví dụ: gọi một API cho phép bạn lấy về danh sách đơn hàng phân trang và thống kê có bao nhiêu đơn hàng đã hoàn thành, đã giao hàng hoặc huỷ… (theo mốc thời gian là tất cả hoặc theo tháng năm…). Nếu dữ liệu của bạn nhiều thì việc gọi API để tải dữ liệu về bạn phải đợi vài giây, để tải được danh sách và kết quả thống kê, điều này làm cho ứng dụng của bạn có vẻ lắc hoặc giựt, không mượt mà.

module.exports = {
fetchOrder: async (req, res) => {
    try {
      let pageSize = 10;
      let currentPage = 1;
      if (typeof req.body['pageSize'] !== 'undefined') {
        pageSize = parseInt(req.body.pageSize);
      }

      if (typeof req.body['currentPage'] !== 'undefined') {
        currentPage = parseInt(req.body.currentPage);
      }

      let orderQuery = { orderStatus: 'publish' };
      let aggregateorderQuery = {orderStatus: 'publish'};
      if( typeof req.body['orderStatus'] !== 'undefined' && req.body['orderStatus'] ) {
        orderQuery.orderStatus = req.body['orderStatus'];
        aggregateorderQuery.orderStatus = req.body['orderStatus'];
      }
      
      let query_date = {};
      if (typeof req.body['from'] !== 'undefined' && req.body['from'] !== null) {
        query_date = { ...query_date, '$gte': new Date(moment(`${req.body['from']} 00:00:00`, 'MM/DD/YYYY H:mm:ss')) };
      }
      if (typeof req.body['to'] !== 'undefined' && req.body['to'] !== null) {
        query_date = { ...query_date, '$lte': new Date(moment(`${req.body['to']} 23:59:59`, 'MM/DD/YYYY H:mm:ss')) };
      }

      if (!_.isEmpty(query_date)) {
        orderQuery = { ...orderQuery, createdAt: query_date };
        aggregateorderQuery = { ...aggregateorderQuery, createdAt: query_date };
      }

      if (typeof req.body['paymentMethod'] !== 'undefined'
        && req.body.paymentMethod !== 'all'
      ) {
        orderQuery.paymentMethod = req.body.paymentMethod;
        aggregateorderQuery.paymentMethod = req.body.paymentMethod;
      }

      if (typeof req.body['orderId'] !== 'undefined'
      ) {
        const orderId = slug(req.body.orderId, { lowercase: true });
        orderQuery.orderId = new RegExp(`${orderId}`, "i");
        aggregateorderQuery.orderId = new RegExp(`${orderId}`, "i");
      }

      if (typeof req.body['paymentStatus'] !== 'undefined'
        && req.body.paymentStatus !== 'all'
      ) {
        let paymentStatus = req.body['paymentStatus'];
        paymentStatus = paymentStatus.split('|');
        orderQuery.paymentStatus = {
          $in: paymentStatus
        };
        orderQuery.status = {
          $ne: 'cancel'
        }
      }
      const list = await Order.find({ ...orderQuery })
        .populate('member')
        .populate('staff')
        .limit(pageSize)
        .skip((currentPage - 1) * pageSize)
        .sort('-createdAt');
      const total = await Order.find({ ...orderQuery }).count();

      
      // Order status counter
      const order_status_counter = await OrderStatusCounter
        .aggregate()
        .match({
          ...aggregateorderQuery
        })
        .group({
          _id: {
            status: '$status'
          },
          count: { $sum: 1 }
        })
        .project({
          "_id": 0,
          "status": "$_id.status",
          "count": "$count"
        });

      
      // Order status extend counter
      const order_status_extend_counter = await Order
        .aggregate()
        .match({
          ...aggregateorderQuery,
          status: 'finish',
          transferredProfit: false,
          netProfit: {
            $gt: 0
          }
        })
        .group({
          _id: '',
          count: { $sum: 1 }
        })
        .project({
          "_id": 0,
          "status": "not_transferred_profit",
          "count": "$count"
        });
      return {
        order_status_counter,
        order_status_extend_counter,
        data: {
          list,
          pagination: {
            currentPage,
            pageSize,
            total,
            current: currentPage,
          }
        }

      };
    } catch (e) {
    }
  }
}

Đoạn thống kê của bạn nó nằm ở phần mã này:

// Order status counter
      const order_status_counter = await OrderStatusCounter
        .aggregate()
        .match({
          ...aggregateorderQuery
        })
        .group({
          _id: {
            status: '$status'
          },
          count: { $sum: 1 }
        })
        .project({
          "_id": 0,
          "status": "$_id.status",
          "count": "$count"
        });

      
      // Order status extend counter
      const order_status_extend_counter = await Order
        .aggregate()
        .match({
          ...aggregateorderQuery,
          status: 'finish',
          transferredProfit: false,
          netProfit: {
            $gt: 0
          }
        })
        .group({
          _id: '',
          count: { $sum: 1 }
        })
        .project({
          "_id": 0,
          "status": "not_transferred_profit",
          "count": "$count"
        });

và để chạy một API như vậy bạn sẽ phải đợi vài giây cho một lần yêu cầu

Để giải quyết vấn đề này, mình sài tới một API của nodeJS đó là child_process. Vậy child process trong nodeJS là gì? khi nào cần sử dụng. Với child_process nó cho phép bạn thực thi 1 tiến trình con để xử lý việc tính toán phức tạp, bằng cách tách hàm lấy dữ liệu phân trang ra 1 API và hàm tính toán thống kê ra 1 API, và việc tính toán thống kê sẽ được 1 tiến trình con xử lý (ở đây mình sài fork). Khi sài như vậy việc trả về danh sách của bạn và việc tính chạy chạy song song và không block lẫn nhau, ứng dụng vẫn tải nhanh và việc tải thông kê nào xử lý xong nó sẽ load dữ liệu sau, làm cho ứng dụng của bạn có vẻ mượt mà hơn

module.exports = {
    orderAggregation: async (req, res) => {
        let pageSize = 10;
        let currentPage = 1;
        if (typeof req.body['pageSize'] !== 'undefined') {
            pageSize = parseInt(req.body.pageSize);
        }

        if (typeof req.body['currentPage'] !== 'undefined') {
            currentPage = parseInt(req.body.currentPage);
        }

        let orderQuery = {};
        let aggregateorderQuery = {};

        let query_date = {};
        if (typeof req.body['from'] !== 'undefined' && req.body['from'] != null) {
            query_date = { ...query_date, '$gte': `${req.body['from']} 00:00:00` };
        }
        if (typeof req.body['to'] !== 'undefined' && req.body['to'] != null) {
            query_date = { ...query_date, '$lte': `${req.body['to']} 23:59:59` };
        }
        if (!_.isEmpty(query_date)) {
            orderQuery = { ...orderQuery, createdAt: query_date };
            aggregateorderQuery = { ...aggregateorderQuery, createdAt: query_date };
        }


        if (typeof req.body['orderId'] !== 'undefined'
        ) {
            const orderId = slug(req.body.orderId, { lowercase: true });
            orderQuery.orderId = new RegExp(`${orderId}`, "i");
            aggregateorderQuery.orderId = new RegExp(`${orderId}`, "i");
        }

        const orderAggregationPath = path.resolve(__dirname, '..', 'libs/orderAggregation.js');
        const child_process = cp.fork(orderAggregationPath);
        child_process.send({ aggregateorderQuery });

        const data = await new Promise((resovle) => {
            child_process.on('message', (data) => {
                child_process.kill('SIGINT');
                return resovle(data);
            })
        });
        return data;
    }
}

Đoạn mã thống kê bên trên

const orderAggregationPath = path.resolve(__dirname, '..', 'libs/orderAggregation.js');
        const child_process = cp.fork(orderAggregationPath);
        child_process.send({ aggregateorderQuery });

        const data = await new Promise((resovle) => {
            child_process.on('message', (data) => {
                child_process.kill('SIGINT');
                return resovle(data);
            })
        });

Nội dung đoạn mã của file orderAggregation.js

const mongoose = require('mongoose');
require('./db');
const {
    Order
} = require('../models');

process.on('message', async (data) => {
    let { aggregateorderQuery}  = data;
    if( typeof aggregateorderQuery['createdAt'] !== 'undefined') {
        aggregateorderQuery['createdAt']['$gte'] = new Date(aggregateorderQuery['createdAt']['$gte']);
        aggregateorderQuery['createdAt']['$lte'] = new Date(aggregateorderQuery['createdAt']['$lte']);
    }
    const all_finish_order_statistics = await Order
        .aggregate()
        .match({
            ...aggregateorderQuery,
            ...memberQuery,
            status: 'finish',
            orderStatus: 'publish'
        })
        .group(
            {
                _id: '',
                totalPrice: {
                    $sum: '$totalPrice'
                }
            }
        )
        .project({
            _id: 0,
            totalPrice: '$totalPrice'
        });
    const all_order_statistics = await Order
        .aggregate()
        .match({
            ...aggregateorderQuery,
            orderStatus: 'publish',
            status: {
                $ne: 'cancel'
            }
        })
        .group(
            {
                _id: '',
                totalPrice: {
                    $sum: '$totalPrice'
                }
            }
        )
        .project({
            _id: 0,
            totalPrice: '$totalPrice'
        });
    
    process.send({ 
        all_finish_order_statistics,
        all_order_statistics
    });
});

process.on('exit', (code) => {
    console.log(`Exit process ${code}`); 
});

cho phép bạn xử lý việc gọi hàm tốn nhiều thời gian, bằng cách gọi child_process.fork(absolute_path). và sau khi đoạn mã bên trong file orderAggregation xử lý xong thì child_process nó nhận kết quả trả về bằng phương thức

child_process.on('message', (data) => {
                child_process.kill('SIGINT');
                return resovle(data);
            })

Ở trên mình sài một Promise để xử lý có kết quả trả về từ child_process và trả về dữ liệu cho client.

Chú ý khi sài child_process.fork là trong sự kiện on(‘message’) bạn cần gọi 1 hàm child_process.kill(‘SIGINT’) để huỷ tiến trình con này.

Rate this post

You May Also Like

About the Author: truongluu

Leave a Reply

Your email address will not be published. Required fields are marked *