js五种异步实现方式对比
最近研究了一下 koa1 和 koa2 的源码,其中比较重要的就是中间件的流程控制,其中 koa1 是通过 generator 实现中间件流程控制,koa2 则是通过 async 函数,说起来利用 js 实现异步流程控制的方法也不少了,所以做一下总结对比。主要总结了五种实现方式:
- callback
- promise
- async
- thunk 版 generator
- promise 版 generator
具体代码可以看我在 codepen 上的 demo,分别打开各方法注释即可。整体代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>async</title>
<style>
.ball {
width: 40px;
height: 40px;
margin-bottom: 20px;
border-radius: 50%;
}
.ball1 {
background-color: red;
}
.ball2 {
background-color: teal;
}
.ball3 {
background-color: deeppink;
}
</style>
</head>
<body>
<h1>五种异步流程控制的实现</h1>
<div class="ball ball1" style="margin-left:0;"></div>
<div class="ball ball2" style="margin-left:0;"></div>
<div class="ball ball3" style="margin-left:0;"></div>
<script>
var ball1 = document.querySelector('.ball1');
var ball2 = document.querySelector('.ball2');
var ball3 = document.querySelector('.ball3');
// 回调函数版的动画
function animate(ball, distance, cb) {
setTimeout(function () {
var marginLeft = parseInt(ball.style.marginLeft, 10);
if (marginLeft == distance) {
cb && cb('德玛西亚');
} else {
if (marginLeft < distance) {
marginLeft++;
} else {
marginLeft--
}
ball.style.marginLeft = marginLeft + 'px';
animate(ball, distance, cb);
}
}, 13)
}
// promise 版的动画实现
function promiseAnimate(ball, distance) {
return new Promise(function (resolve, reject) {
function _animate() {
setTimeout(function () {
var marginLeft = parseInt(ball.style.marginLeft, 10);
if (marginLeft == distance) {
resolve()
} else {
if (marginLeft < distance) {
marginLeft++;
} else {
marginLeft--
}
ball.style.marginLeft = marginLeft + 'px';
_animate()
}
}, 13)
}
_animate()
})
}
//方法一,利用回调函数实现
// animate(ball1, 100, function () {
// animate(ball2, 200, function () {
// animate(ball3, 300, function () {
// animate(ball3, 150, function () {
// animate(ball2, 150, function () {
// animate(ball1, 150, function () {
// })
// })
// })
// })
// })
// })
// 方法二 利用promise实现
// promiseAnimate(ball1,100)
// .then(function () {
// return promiseAnimate(ball2,200)
// })
// .then(function () {
// return promiseAnimate(ball3,300)
// })
// .then(function () {
// return promiseAnimate(ball3,150)
// })
// .then(function () {
// return promiseAnimate(ball2,150)
// })
// .then(function () {
// return promiseAnimate(ball1,150)
// })
// 方法三 利用 async await 实现
// (async function () {
// await promiseAnimate(ball1, 100)
// await promiseAnimate(ball2, 200)
// await promiseAnimate(ball3,150)
// await promiseAnimate(ball2,150)
// await promiseAnimate(ball1,150)
// })()
// 方法四 基于 promise 的 generator 实现
// function run(gen){
// var g = gen();
// function next(data){
// var result = g.next(data);
// if (result.done) return result.value;
// result.value.then(function(data){
// next(data);
// });
// }
// next();
// }
// var movePromise = function* (){
// yield promiseAnimate(ball1, 100);
// yield promiseAnimate(ball2, 200);
// yield promiseAnimate(ball3, 150);
// yield promiseAnimate(ball2, 150);
// yield promiseAnimate(ball1, 150);
// };
// run(movemovePromise)
// 方法五 基于 thunk 的 generator 实现
function thunkify(fn) {
return function () {
var args = new Array(arguments.length);
var ctx = this;
for (var i = 0; i < args.length; ++i) {
args[i] = arguments[i];
}
return function (done) {
var called;
args.push(function () {
if (called) return;
called = true;
done.apply(null, arguments);
});
try {
fn.apply(ctx, args);
} catch (err) {
done(err);
}
}
}
};
var animateThunk = thunkify(animate)
function run(fn) {
var gen = fn();
function next(value) {
console.log(value)
var result = gen.next();
if (result.done) return;
result.value(next);
}
next();
}
var moveThunk = function* () {
yield animateThunk(ball1, 100)
yield animateThunk(ball2, 200)
yield animateThunk(ball3, 150)
yield animateThunk(ball2, 150)
yield animateThunk(ball1, 150)
};
run(moveThunk)
</script>
</body>
</html>
下面简单看一下最终各中方式实现代码:
callback
animate(ball1, 100, function () {
animate(ball2, 200, function () {
animate(ball3, 300, function () {
animate(ball3, 150, function () {
animate(ball2, 150, function () {
animate(ball1, 150, function () {
})
})
})
})
})
})
promise
promiseAnimate(ball1,100)
.then(function () {
return promiseAnimate(ball2,200)
})
.then(function () {
return promiseAnimate(ball3,300)
})
.then(function () {
return promiseAnimate(ball3,150)
})
.then(function () {
return promiseAnimate(ball2,150)
})
.then(function () {
return promiseAnimate(ball1,150)
})
async
(async function () {
await promiseAnimate(ball1, 100)
await promiseAnimate(ball2, 200)
await promiseAnimate(ball3,150)
await promiseAnimate(ball2,150)
await promiseAnimate(ball1,150)
})()
基于 promise 的 generator 实现
var movePromise = function* (){
yield promiseAnimate(ball1, 100);
yield promiseAnimate(ball2, 200);
yield promiseAnimate(ball3, 150);
yield promiseAnimate(ball2, 150);
yield promiseAnimate(ball1, 150);
};
run(movemovePromise) // 需要自己实现 run 函数
基于 thunk 的 generator 实现
var moveThunk = function* (){
yield animateThunk(ball1, 100)
yield animateThunk(ball2, 200)
yield animateThunk(ball3, 150)
yield animateThunk(ball2, 150)
yield animateThunk(ball1, 150)
};
run(moveThunk) // 同样需要自己 run 函数
总结
callback 写法最不直观,当有多个连续执行异步操作任务时,容易造成回调地狱。其余四种都是同步写法实现异步流程控制,其中 async 函数相当于自动执行的 Promise 以及后边的 generator 函数,如果使用 generator,需要自己实现一个 run 函数来进行自动化执行,这里就要提到著名的 co 库。总的来说还是 async 不错。
另外,能力有限,可能存在表达错误。另外具体各种方法实现,这里不做详细说明,可以参考下边提到的参考文章。
参考文章: