javascript中的异步流程控制

155 阅读3分钟

本文以代码为主,不会对代码做详细的讲解,相信大家都可以看的懂哦。先从异步带来的回调地狱问题开始,然后总结出来了多种解决异步流程控制的代码写法,希望对大家有帮助。

回调地狱

function delayFunc(){
  setTimeout(() => {
    console.log("t1");
    setTimeout(() => {
      console.log("t2");
      setTimeout(() => {
        console.log("t3");
      }, 1000);
    }, 1000);
  }, 1000);
}

delayFunc();

rxjs中的流的控制,subscribe里套subscribe,套一层的话,影响不是很大,如果套3层或3层以上,阅读起来很不友好,一般用switchMap来避免这种问题。

上面代码挺清楚的呀?一眼就看到执行顺序和逻辑?? NoNoNO

经典案例

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>回调地狱</title>
    <style>
        .box {
            width: 100px;
            height: 100px;
            background: red;
            position: absolute;
            left: 0px;
            top: 0px;
        }
    </style>
</head>

<body>
    <div class="box"></div>
    <script>
        function move(ele, dir, target, cb) {
            let start = parseInt(getComputedStyle(ele, null)[dir]);
            let speed = 5 * (target > start ? 1 : -1);

            setTimeout(() => {
                let temp = start + speed;

                if (temp === target) {
                    cb && cb();
                } else {
                    ele.style[dir] = temp + 'px';
                    move(ele, dir, target, cb);
                }
            }, 20);
        }

        let ele = document.querySelector('.box');
        move(ele, 'left', 300, function() {
            move(ele, 'top', 300, function() {
                move(ele, 'left', 0, function() {
                    move(ele, 'top', 0, function() {
                        console.log('完成');
                    });
                });
            });
        });
    </script>
</body>

</html>

解决方案

具名函数

function delayFunc(msg, cb){
  setTimeout(() => {
    console.log(msg);
    cb && cb(msg);
  }, 1000);
}

function func1(){
  delayFunc("t2", func2);
}
function func2(){
  delayFunc("t3", func3);
}
function func3(){
  delayFunc("t4");
}

delayFunc("t1", func1);

观察者模式

严格来说是,订阅发布模式:

let eventObj = new EventTarget();

eventObj.addEventListener("event1", function(e){
    console.log(e);
});

eventObj.dispatchEvent(new CustomEvent("event1", {
    detail: {
        age: 20
    }
}));
let eventObj = new EventTarget();
let num = 1;

function move(ele, dir, target) {
  let start = parseInt(getComputedStyle(ele, null)[dir]);
  let speed = 5 * (target > start ? 1 : -1);

  setTimeout(() => {
    let temp = start + speed;

    if (temp === target) {
      eventObj.dispatchEvent(new CustomEvent('myEvent' + num));
      num++;
    } else {
      ele.style[dir] = temp + 'px';
      move(ele, dir, target);
    }
  }, 20);
}

let ele = document.querySelector('.box');
move(ele, 'left', 300);
eventObj.addEventListener('myEvent1', function() {
  move(ele, 'top', 300);
});
eventObj.addEventListener('myEvent2', function() {
  move(ele, 'left', 0);
});
eventObj.addEventListener('myEvent3', function() {
  move(ele, 'top', 0);
});
eventObj.addEventListener('myEvent4', function() {
  console.log('运动完成');
});

thunk函数

thunk函数可用解决回调地狱的问题。thunk函数是只接收一个回调函数参数的函数,函数的thunk化指的是,讲一个多参函数变为一个只接收回调函数参数的函数。

先来一个简单的例子,函数中只有一个参数msg。

function delayFunc(msg, cb){
  setTimeout(() => {
    console.log(msg);
    cb && cb();
  }, 1000);
}

let delayFuncThunk = (msg) => (cb) => {
  delayFunc(msg, cb)
};

let t1 = delayFuncThunk("t1");
let t2 = delayFuncThunk("t2");
let t3 = delayFuncThunk("t3");
let t4 = delayFuncThunk("t4");

// t1(() => t2(() => t3(() => t4())));
let arr = [t1, t2, t3, t4];
let gen = (arr) => {
  return arr.reduceRight((prev, cur) => {
    return () => cur(() => prev());
  });
}

let g = gen(arr);
g();

用reduce函数怎么实现? 简单的来,可以把arr = [t4, t3, t2, t1];

思考?

如果函数中有不确定参数个数,如果优雅的实现thunk化?

function delayFunc(msg,title,name,arg1...){
    ...
}

let delayFuncThunk = ...

generator函数

generator函数设计的初衷,是为了优雅的实现迭代,并不是为了异步流程控制,但是可以使用generator函数来实现异步控制。

function delayFunc(msg, cb){
  setTimeout(() => {
    console.log(msg);
    cb && cb();
  }, 1000);
}

function * myGen(cb){
  yield delayFunc("t1", cb);
  yield delayFunc("t2", cb);
  yield delayFunc("t3", cb);
  yield delayFunc("t4", cb);
}

const runner = myGen(goNext);
function goNext(res){
  runner.next(res);
}
runner.next();

优化下代码:

function run(gen){
  let it = gen(goNext);

  // function goNext(){
  //  it.next();
  // }
  function goNext(res){
    it.next(res);
  }

  goNext();
}

run(myGen);

思考: 如果在generator函数中,yield有返回值,怎么写?

thunk + generator

function delayFunc(msg, cb){
  setTimeout(() => {
    console.log(msg);
    cb && cb();
  }, 1000);
}

let delayFuncThunk = (msg) => (cb) => {
  delayFunc(msg, cb);
};

let t1 = delayFuncThunk("t1");
let t2 = delayFuncThunk("t2");
let t3 = delayFuncThunk("t3");
let t4 = delayFuncThunk("t4");

function * myGen(){
  yield t1;
  yield t2;
  yield t3;
  yield t4;
}

function run(gen){
  const it = gen();

  function goNext(){
    const temp = it.next(...arguments);
    if(!temp.done) {
      temp.value(goNext);
    }
  }

  goNext();
}

run(myGen);

第二种写法:

function delayFunc(msg, cb) {
  setTimeout(() => {
    console.log(msg);
    cb && cb();
  }, 1000);
}

let delayFuncThunk = (msg) => (cb) => {
  delayFunc(msg, cb);
};

let t1 = delayFuncThunk("t1");
let t2 = delayFuncThunk("t2");
let t3 = delayFuncThunk("t3");
let t4 = delayFuncThunk("t4");

function* myGen() {
  yield t1;
  yield t2;
  yield t3;
  yield t4;
}

function run(gen){
  const it = myGen();
  
  // () => t1()
  // () => t2(() => t1())
  // () => t3(() => t2(() => t1()))
  // () => t4(() => t3(() => t2(() => t1())))
  function goNext(cb){
    const temp = it.next();
    if (!temp.done) {
      if(cb) {
        goNext(() => temp.value(cb));
      }
      else {
        goNext(() => temp.value());
      }
    }
    else {
      cb();
    }
  }

  goNext();
}

run(myGen);

promise

function delayFunc(msg, cb){
  setTimeout(() => {
    console.log(msg);
    cb && cb();
  }, 1000);
}

const delayFuncPromise = (msg) => {
  return new Promise((resolve) => {
    delayFunc(msg, resolve);
  });
};

delayFuncPromise("p1")
  .then(() => {
  return delayFuncPromise("p2");
})
  .then(() => {
  return delayFuncPromise("p3");
})
  .then(() => {
  console.log("完成了!");
});

promise + generator

function delayFunc(msg, cb){
  setTimeout(() => {
    console.log(msg);
    cb && cb();
  }, 1000);
}

const delayFuncPromise = (msg) => {
  return new Promise((resolve) => {
    delayFunc(msg, resolve);
  });
};

function *myGen(){
  yield delayFuncPromise("p11");
  yield delayFuncPromise("p22");
  yield delayFuncPromise("p33");
}

function run(gen){
  const it = gen();

  function goNext(){
    const temp = it.next(...arguments);
    if(!temp.done) {
      temp.value.then(goNext);
    }
  }

  goNext();
}

run(myGen);

async/await

async/await语法糖天然支持Promise,是js中异步的终极解决方案。

function delayFunc(msg, cb){
  setTimeout(() => {
    console.log(msg);
    cb && cb();
  }, 1000);
}

const delayFuncPromise = (msg) => {
  return new Promise((resolve) => {
    delayFunc(msg, resolve);
  });
};


async function myAsyncFunc(){
  let res = await delayFuncPromise("ps1");
  await delayFuncPromise("ps2");
  await delayFuncPromise("ps3");
}

myAsyncFunc();

思考

柯里化

function myFunc1(arg1,arg2,arg3,arg4,arg5){
    console.log(arg1,arg2,arg3,arg4,arg5);

    return arg1 + arg2 + arg3 + arg4 + arg5;
}

function curryFunc(func){
    let length = func.length;

    return function curry(...args){
        if (args.length < length) {
            return function(){
               return curry(...args, ...arguments);
            };
        }

        return func(...args);
    }
}

let f = curryFunc(myFunc1);
let res = f(1)(2)(3)(4)(5);
console.log(res);