使用过js的开发者都知道JavaScript是单线程的,所谓的单线程是一次只能完成一种任务,若还有其他任务,需等待前一个任务执行完成后再来执行该任务。那么问题来了,假设我需要同时执行多个任务呢,单线程又如何能满足这种需求呢?
js引入了同步和异步,所谓同步就是上述所说的,另一个任务需要等待当前任务完成之后再执行,而异步呢指的是当前任务有一个或多个回调函数,当该任务执行完会触发回调函数的执行,而后一个任务也不必等待前一个任务执行完再执行,它的执行与前一个任务是否执行完无关。
我们常常遇到的就是浏览器的ajax请求,它是一种耗时操作,在该浏览器向服务器发送请求数据时,不必等待服务器返回数据,也可以做一些其它的操作或者任务,等服务器返回数据,会触发一个回调函数通知数据已经被请求回来了。
接下来我们通过一个例子一起来看下关于js异步流程的前世今生:
假设有3个小球A,B,C,A球在4ms内向右移动100px,接着是B球以同样时间,移动同样的距离,接着是C。
回调函数
以下给出部分实现的关键代码,这是一种很常规的使用回调函数来解决异步流程的问题,先执行完小球A,然后执行小球A成功的回调函数,一层一层依次嵌套,直至小球C。
doSport() {
// 1.回调
this.move(this.ref1, 100, () => {
this.move(this.ref2, 100, () => {
this.move(this.ref3, 100, () => {
console.log('stop sport');
})
})
})
}
move(ele, target, callback) {
let that = this;
return new Promise(function(resolve, reject){
let count = 0;
let timer = setInterval(() => {
if (count < target) {
ele.current.style.transform = `translateX(${++count}px)`;
} else {
clearInterval(timer);
resolve();
callback && callback();
}
}, 4);
})
}
可以看出上述代码存在一些问题,如果有10个还好,20个呢甚至100个小球呢?那么是不是会造成代码地狱呢,层级嵌套太深,虽然可读性好,但是非常难维护,审美也很差。
Promise
于是有了Promise的实现方式,通过then方法执行上述回调函数。
doSport() {
// 2.promise
this.move(this.ref1, 100).then(() => {
this.move(this.ref2, 100).then(() => {
this.move(this.ref3, 100).then(() => {
console.log('stop sport');
})
})
});
}
move(ele, target, callback) {
let that = this;
return new Promise(function(resolve, reject){
let count = 0;
let timer = setInterval(() => {
if (count < target) {
ele.current.style.transform = `translateX(${++count}px)`;
} else {
clearInterval(timer);
resolve();
callback && callback();
}
}, 4);
})
}
move方法返回一个promise,通过then方法执行它的下一步操作。promise是CommonJS工作提出的一种规范,目的是为异步编程提供统一接口,它的思想是,每一个异步任务返回一个promise,then方法处理下一步的操作,成功态执行resolve函数,失败态就执行reject函数。
Generator
接着提出了Generator函数,在函数名前加上*,内部函数结合yield,当然最好的是搭配上co函数,该函数是可以帮你自动执行迭代器,从下面的代码我们可以看到通过用yield的异步方式实现同步操作,结合co返回一个promise,最后通过then方法执行任务成功的函数。
// 3.generator
*doSport() {
yield this.move(this.ref1, 100);
yield this.move(this.ref2, 100);
yield this.move(this.ref3, 100);
}
// co:自动执行完迭代器
co(it) {
return new Promise(function(resolve, reject) {
function next(d) {
let { value, done } = it.next(d);
if (!done) {
value.then(function(data) {
next(data)
}, reject);
} else {
resolve(value)
}
}
next()
})
}
co(doSport()).then(function() {
alert('ok');
})
move(ele, target, callback) {
let that = this;
return new Promise(function(resolve, reject){
let count = 0;
let timer = setInterval(() => {
if (count < target) {
ele.current.style.transform = `translateX(${++count}px)`;
} else {
clearInterval(timer);
resolve();
callback && callback();
}
}, 4);
})
}
由于generator函数需要搭配co函数,要手动的执行内部的next方法,而且不够直观,没有语义化。
Async/await
es7推出了Async/await的新语法,而且babel也支持,用async修饰函数,内部结合使用await,这种方式我个人觉得很优雅,很容易使用,其实它的内部实现也是gennerator
// 4.async/await
async doSport() {
await this.move(this.ref1, 100);
await this.move(this.ref2, 100);
await this.move(this.ref3, 100);
}
move(ele, target, callback) {
let that = this;
return new Promise(function(resolve, reject){
let count = 0;
let timer = setInterval(() => {
if (count < target) {
ele.current.style.transform = `translateX(${++count}px)`;
} else {
clearInterval(timer);
resolve();
callback && callback();
}
}, 4);
})
}
async函数可以看做多个异步操作,包装成一个promise对象,而await就是内部then方法的语法糖,相比较于gennerator函数来说,async/await函数更加直观,语义化,而且更加好用,不需要co函数。
总结
总的来说,不同的异步方案都是解决异步流程的方式,但是异步编程的本质并没有变,只是对于开发者来说更加的友好,易于维护。时代在发展,技术也在日益更新,我们更重要的是要掌握背后的编程思想原理,这样才能更上一层楼。