js异步任务编排方式

48 阅读3分钟

JS是单线程在主线程中执行,同步任务是按照步骤一步一步执行,但是遇到异步任务会打乱程序执行顺序,如果出现复杂场景就会遇到多个异步任务的串行、并行、混合执行、批量控制等情况,下面就来看下不同的情况是实现思路。

1.通用的数据结构

需要用一个队列存储js的任务,把任务先收集起来

 let queue = [];
 queue.push(task);

同步执行(按顺序执行)

多个异步任务按顺序依次执行,前一个任务完成后再开始下一个。

实现方式:
  • Promise.then 链式调用
  • async/await 同步风格写法

其实就是遍历这个队列,可以用循环或者递归的方式。


// forEach
Promise.resolve().then(() => { 
   let sequence = Promise.resolve()
   queue.forEach(item => {
     sequence = sequence.then(item) 
   })
})

//reduce
queue.reduce((acc, item) => acc.then(()=>{ 
   return item(); 
}), Promise.resolve()); // 串行执行

// 递归(koa洋葱模型)
const dispatch = async (i) => {
    if (i < this.middlewares.length) {
       await Promise.resolve(queue[i](ctx, () => dispatch(i + 1)));
    }
};
   
// 使用for...of
async function f() {
    for (let item of arr) {
        await item();
    }
}

// 使用for循环
async function f() {
    for (let i = 0; i < arr.length; i++) {
        await arr[i]();
    }
}
 
// 递归
async function serial(arr) {
    let item = arr.shift();

    await item();
    if (arr.length > 0) {
        await serial(arr);
    }
}
 

2.并行执行(同时执行)

多个异步任务同时启动,等待所有任务完成后再进行下一步处理。

实现方式:
  • Promise.all:所有任务成功完成才返回,任何一个失败则立即失败
  • Promise.allSettled:等待所有任务完成(无论成功或失败),返回所有结果
// 模拟异步任务(可能成功或失败)
function asyncTask(name, delay = 1000, shouldFail = false) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (shouldFail) {
        console.log(`任务 ${name} 失败`);
        reject(new Error(`任务 ${name} 执行失败`));
      } else {
        console.log(`任务 ${name} 完成`);
        resolve(`结果:${name}`);
      }
    }, delay);
  });
}

// 方式1:Promise.all - 全成功才成功,有一个失败则整体失败
async function runAllTasks() {
  try {
    const [resultA, resultB, resultC] = await Promise.all([
      asyncTask('A', 1000),
      asyncTask('B', 1500),
      asyncTask('C', 800)
    ]);
    console.log('所有任务成功完成:', resultA, resultB, resultC);
  } catch (error) {
    console.log('有任务失败:', error.message);
  }
}

// 方式2:Promise.allSettled - 等待所有任务完成,无论成功失败
async function runAllSettledTasks() {
  const results = await Promise.allSettled([
    asyncTask('A', 1000),
    asyncTask('B', 1500, true), // 这个任务会失败
    asyncTask('C', 800)
  ]);
  
  // 处理结果
  const successes = [];
  const failures = [];
  
  results.forEach((result, index) => {
    if (result.status === 'fulfilled') {
      successes.push({ task: index, value: result.value });
    } else {
      failures.push({ task: index, reason: result.reason.message });
    }
  });
  
  console.log('成功的任务:', successes);
  console.log('失败的任务:', failures);
}

3. 竞争执行(取最快完成的任务)

多个异步任务同时启动,只需要最快完成的那个任务的结果。

// 模拟不同延迟的异步任务
function fetchServer(name, delay) {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(`${name}`);
      resolve(`${name}`);
    }, delay);
  });
}

// 模拟超时任务
function timeoutTask(ms) {
  return new Promise((_, reject) => {
    setTimeout(() => {
      reject(new Error(`超过 ${ms}ms 未响应`));
    }, ms);
  });
}

// 场景1:从多个服务器获取数据,使用最快响应的结果
async function getFastestData() {
  try {
    const result = await Promise.race([
      fetchServer('1', 1000),
      fetchServer('2', 800),  // 这个会最先完成
      fetchServer('3', 1200)
    ]);
    console.log(result);
  } catch (error) {
    console.log('错误:', error.message);
  }
}

// 场景2:为异步任务设置超时限制
async function fetchWithTimeout() {
  try {
    const result = await Promise.race([
      fetchServer('1', 1500),
      timeoutTask(1000)  // 1秒超时
    ]);
    console.log('获取数据成功:', result);
  } catch (error) {
    console.log('获取数据失败:', error.message); // 会触发超时错误
  }
}

// getFastestData();
// fetchWithTimeout();

实现方式:
  • Promise.race:返回第一个完成(无论成功或失败)的任务结果

4. 复杂依赖编排(混合串行与并行)

实际业务中常遇到更复杂的依赖关系,需要结合串行和并行方式。

// 分成多批,一批一批执行
async function fnBatch(arr, num) {
    let items = arr.splice(0, num);
    
    await Promise.all(items.map(i => i()));
    if (arr.length > 0) {
        await fnBatch(arr, num);
    }
}
fnBatch(arr, 2);

// 按照最大请求数同时请求,完成一个加入一个新的
function promiseAllLimit(maxNums, arr) {
  if (!arr.length) {
    return;
  }
  let k = 0;
  let result = [];
  return new Promise((resolve, reject) => {
    function next() {
      if (k === arr.length) {
        return;
      }
      let i = k++;
      arr[i]()
        .then((res) => {
          result[i] = res;
        })
        .catch((err) => {
          result[i] = err;
        })
        .finally(() => {
          if (i < arr.length) {
            next();
          } else {
            resolve(result);
          }
        });
    }
    while (k < maxNums) {
      next();
    }
  });
}

const nums = new Array(100).fill(1).map((num, i) => i + 1);
const asyncDouble = (num) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      return resolve(num * 2);
    }, 200);
  });
};
console.log(
  nums.map((num) => {
    return () => asyncDouble(num);
  })
);
const start = Date.now();
promiseAllLimit(
  2,
  nums.map((num) => {
    return () => asyncDouble(num);
  })
).then((r) => {
  console.log("res", r);
  const end = Date.now();
  console.log(end - start);
});

5. 使用工具库简化编排(如 async.js)

  • 特点:最经典的异步流程控制库,提供了丰富的串行、并行、限流等操作 API

  • 适用场景:复杂的异步任务编排、批量操作控制

  • 核心 API

    • async.series:串行执行
    • async.parallel:并行执行
    • async.waterfall:带参数传递的串行执行
    • async.mapLimit:限制并发数的批量处理

// 注意:需先安装async库(npm install async)
const async = require('async');

// 模拟异步任务
function task(name, delay, callback) {
  setTimeout(() => {
    console.log(`任务 ${name} 完成`);
    callback(null, name); // 第一个参数为错误,第二个为结果
  }, delay);
}

// 1. 串行执行(series)
async.series([
  (callback) => task('A', 1000, callback),
  (callback) => task('B', 800, callback),
  (callback) => task('C', 1200, callback)
], (err, results) => {
  if (err) console.error('串行任务失败:', err);
  else console.log('串行任务结果:', results);
});

// 2. 并行执行(parallel)
async.parallel([
  (callback) => task('X', 1000, callback),
  (callback) => task('Y', 800, callback),
  (callback) => task('Z', 1200, callback)
], (err, results) => {
  if (err) console.error('并行任务失败:', err);
  else console.log('并行任务结果:', results);
});

// 3. 限速并行(limit):同时最多执行2个任务
async.parallelLimit([
  (callback) => task('L1', 1000, callback),
  (callback) => task('L2', 800, callback),
  (callback) => task('L3', 1200, callback),
  (callback) => task('L4', 600, callback)
], 2, (err, results) => {
  if (err) console.error('限速任务失败:', err);
  else console.log('限速任务结果:', results);
});

总结

JavaScript 异步任务编排的核心是根据业务需求选择合适的执行策略:

  • 串行执行:适合有依赖关系的任务(async/await 最直观)
  • 并行执行:适合无依赖关系、需要提高效率的任务(Promise.all/Promise.allSettled
  • 竞争执行:适合需要最快结果或超时控制的场景(Promise.race
  • 复杂依赖:结合串行和并行,用 async/await + Promise.all 组合实现
  • 超复杂场景:借助工具库(如async.js)提供的高级 API

合理的异步任务编排能显著提高程序效率,减少不必要的等待时间,同时让代码逻辑更清晰易懂。