前言
首先,异步(async)
编程是相对于同步(sync)
编程而言,所谓同步编程就是按照代码的执行顺序,一个进程执行完成以后再调用下一个进程的过程,由于JavaScript是单线程的语言,在JS中大部分代码都是同步执行的,但是这会导致一个问问题:如果在调用的过程中,执行了I/O操作或者一些耗时的进程,进程就会阻塞,后面的进程也只能等待前面的进程执行完成以后再执行.因此有了异步编程的出现.下面介绍JS中的四种异步编程模式.
一.回调函数(callback)
回调函数(callback)
是在主函数的参数中传入一个函数,这个传入的函数就是回调函数
,如下:
function say(call_fun) {
console.log(111);//111
call_fun();//这是一个回调函数
}
say(function(){
console.log('这是一个回调函数');
})
那么,回调函数是如何处理异步程序的呢?
回调函数的使用;
可以通过不断的在函数中使用回调函数来实现异步,注意:JS中setTimeout是异步任务
.如下:
setTimeout(() => {
//异步操作
setTimeout(() => {
// 异步操作
setTimeout(() => {
//异步操作
setTimeout(() => {
//异步操作
}, 2000);
}, 2000);
}, 2000);
}, 2000);
通过以上代码可以发现回调函数实现的异步编程有一个致命的缺点,那就是产生回调地狱(callback hell)
,这不仅影响代码的可读性,对后期维护和排错也会产生很多不必要的麻烦.
二.promise
promise
最早是社区提出来的解决方案,最后被纳入官方标准,promise解决的就是回调地狱的问题.
首先来看一下promise
的使用:
通过创建一个Promise
对象,在对象中传入一个回调函数,函数包括两个参数resolve
和reject
,在在异步操作成功以后调用resolve函数
,在异步操作失败以后调用reject函数
.这两个函数都可以继续传入参数,在then()
方法中接收参数.
let prom = new Promise(function(resolve,reject){
resolve('success');//在异步操作成功以后调用
reject('faild');//在异步操作失败以后调用
})
prom.then(function(data){
console.log(data);//resolve调用后进入的函数
},function(data){
console.log(data);//reject调用后进入的函数
})
还有一种写法:
let prom = new Promise(function(resolve,reject){
resolve('success');//在异步操作成功以后调用
reject('faild');//在异步操作失败以后调用
})
prom.then(function(data){
console.log(data);//success resolve调用后进入的函数
}).catch(function(data){
console.log(data);// faild reject调用后进入的函数
})
当在实际应用中需要不断链式调用的时候,我们需要在then
的方法中返回一个新的promise
对象,这样就可以不断链式调用。上面的例子用promise
写法如下:
注意:在promise里面 异步任务以外的操作仍然是同步任务.
let prom = new Promise(function (resolve, reject) {
setTimeout(() => {
//异步操作
console.log(111);
resolve('success');
}, 2000);
})
prom.then(function (data) {
console.log(data);//success
return new Promise(function (resolve, reject) {
setTimeout(() => {
//异步操作
resolve('success1')
}, 2000);
})
}).then(function (data) {
console.log(data);//success1
return new Promise(function (resolve, reject) {
setTimeout(() => {
//异步操作
resolve('success2')
}, 2000);
})
}).then(function (data) {
console.log(data);//success2
setTimeout(() => {
//异步操作
}, 2000);
})
三.生成器(Generators/ yield)
生成器的实质还是使用promise
、生成器
和执行器
来执行异步程序.
生成器的使用:
首先需要封装一个函数,这个函数内部返回promise
对象,在promise
对象内部执行异步操作,通过生成器来保留状态.再调用执行器执行.
上面的程序写法如下:
function test(){
return new Promise(function(resolve,reject){
setTimeout(() => {
//异步操作
resolve();
}, 2000);
})
}
//生成器
function* gen(){
yield test();
yield test();
yield test();
yield test();
}
//执行器
let it =gen();
it.next().value.then(()=>{
return it.next().value;
}).then(()=>{
return it.next().value;
}).then(()=>{
return it.next().value;
}).then(()=>{
return it.next().value;})
通过生成器在一定程度上简化来promise
操作,但是手动迭代还是很麻烦的,在2013年的时候,推出了一个co
模块,使用co
模块就可以自动的执行生成器.
//执行器
co(gen)
四.async/await
async/await
是语法糖,其实质仍是生成器.async
表示执行的函数里面有异步操作,在语义上能够清晰明了的看到.
async/await
的使用:
创建一个返回Promise
的函数,在promise
内部进行异步操作,再生成一个带有异步操作的函数,需要声明async
,函数内部需要进行异步的操作前加上await
,await
后面的代码必须在异步操作结束以后才能执行.
上面的代码用aync/await写法如下:
注意:asyn执行后返回的是一个promise对象,await表示后面的程序需要等待结束以后才能执行.
function test(){
return new Promise(function(resolve,reject){
setTimeout(() => {
//异步操作
resolve();
}, 2000);
})
}
async function read(){
await test();
await test();
await test();
await test();
}
read();
五.总结
1.实现异步的方案从最早的回调函数=>promise=>生成器=>async/await,是在一步一步的基础上改进过来的,没有十全十美的方法,只有不断改进的方案.
2.从目前的情况来看,async/await是异步的最终解决方案.同样的,在不同场景下,每个方法有自己独特的优势,最终的决定权还是每一位开发者.