前端异步编程的串行和并行

1,966 阅读3分钟

当我们有一个异步、同步函数数组时, 对其可能会有串行和并行执行循序的期望。常见的场景有动画的顺序执行,异步请求的顺序、并发执行,以下为几种常见的解决方案。

异步请求的并行

使用Promise.all

const axios = require('axios');
const fetch1 = axios('http://localhost:3000/a').then((res) => res.data);
const fetch2 = axios('http://localhost:3000/p').then((res) => res.data);;

const arrs = [fetch1,fetch2];
Promise.all(arrs).then((res) => {
    console.log('res', res);
});

有数量限制的并行

比如总共有10个异步方法,需要最大并行处理的数量是6,每次仅同时发送6个请求,等到某个异步执行完后再继续执行下一个请求。 异步函数执行下一个函数时通常使用next函数来操作,以下为一个简陋的实现0.0

const axios = require('axios');
function multiRequest(urls, max = 2) {
    if(max <= 0 || urls.length <= 0) {return}
	  const arrs = urls.map((url) => () => axios(url).then((res) => res.data));
    let i = 0;
    let count = arrs.length;
    let result = Array.from({length: count});
    return new Promise((resolve, reject) => {
        function next() {
            if (i === count) {
               return resolve(result);
            }
            let fn = arrs.shift();
            if (fn) {
                let index = count - arrs.length -1;
                fn().then((res) => {
                    result[index] = res;
                    i++;
                    if (i < count) {
                        next();
                    } else {
                        return resolve(result);
                    }
                })
            }
            
        }
        for (let index = 0;index < max; index++) {
            let fn = arrs.shift();
            fn().then((res) => {
                result[index] = res;
                i++;
                next();
            });
        }
    }) 
}

串行执行

常见的异步串行的方式有2种,1.是函数内推执行完后执行next来触发下一个函数的调用, 2.是使用reduce来形成一个promise链依次执行。

reduce的方式

使用reduce, 这种方式比较适合所有需要串行的方法都为Promise的时候。

function d() {
   return new Promise((resolve, reject) => {
       setTimeout(() => {
        console.log('d');
        resolve('D');
       },3000);
   })
}

function e() {
   return new Promise((resolve, reject) => {
       setTimeout(() => {
        console.log('e');
        resolve('E');
       }, 0);
   })
}

function runSequence(jobs) {
    return jobs.reduce((acc, job) => {
        return acc.then(()=> job());
    }, Promise.resolve());
}

runSequence([d, e]).then((res) => {
    console.log('res', res); //res =>E
});
//console.log --> d e

koa-compose的实现方式

大致思路使用是在函数内执行完后执行传入的next函数, 来实现串行执行。为了能够兼容非异步函数,在每个函数外面用Promise.resolve包了一层。这样就能使我们无论是异步函数还是平常的函数都能够按循序进行执行,并能通过.then的方式获得最后的执行结果。

function a(context,next) {
    console.log('a');
    context.a = 'a';
    next()
}

function b(context, next) {
    return new Promise((resolve,reject) => {
        console.log('b');
        context.b = 'b';
        resolve('b');
        next();
    })
}

function c(context,next) {
    console.log('c');
    context.c = 'c';
}
function compose(middlewares, context) {
    function dispatch(i) {
        if(i === middlewares.length) return Promise.resolve();
        let fn = middlewares[i];
        return Promise.resolve(fn(context, () => {
            dispatch(++i);
        }))
    }
    return dispatch(0);
}

let context  = {a: 1, b:2, c:3};
compose([a,b,c], context).then(() => {
    console.log('end');
    console.log(context.a, context.b, context.c);
});
//先依次输出 a, b, c, 
//end
//context的结果 a, b, c

redux中间件的方式

redux实现中间件的方式也是通过一个compose函数将函数进行组合,本身redux中间件的实现也是有借鉴koa的洋葱模型,但redux的实现本质是通过不断用之后的函数来包装之前的函数。

function a(next) {
    console.log('a', 'before');
    return function () {
        console.log('a');
        next()
    } 
}

function b(next) {
   console.log('b', 'before');
    return () => {
        return new Promise((resolve,reject) => {
            console.log('b');
            resolve('b');
            next();
        })
    }
}


function c(next) {
    console.log('c', 'before');
    return () =>{
        console.log('c');
        next();
    }
}

function compose(...fns) {
    if (fns.length === 0)  return (arg) => arg;
    if (fns.length === 1) return fns[0];
    return fns.reduce((a,b) => (...args) => a(b(...args)))
};
//默认compose的执行顺序是从右往左。
let fn = compose(c, b, a)();
fn();
//输出结果为
//a before
//b before
//c before
//c
//b
//a