为什么出现异步?js是单线程的执行,因为同步会堵塞。
异步的形式有哪些:回调函数、事件监听、Promise、Generator、async/await几种形式
异步和同步的区别
同步执行的代码,加入某个js 执行的事件比较长,那么后边的代码都会等待执行,比方说ajax的同步请求,只有这个请求请求完成,其他的js才会执行。如果同步请求堵塞严重,就会造成造成页面请求后的js 没法执行,页面会出现空白等待区域。
异步执行代码,恰恰和同步相反,执行代码到异步请求的时候,会把请求挂起,代码继续执行,等请求完成以后,再去执行异步代码请求的结果。
异步的解决方案
异步的解决方案有:回调函数、事件监听、setTimeout/setInterval(定时请求)、Promise、Generator、async/await,
回调函数
回调函数,是最初的解决方案,为了解决请求回来,继续调用写一个请求的时候使用
fs.readFile('a.txt',(err,data) =>{
if(data) {
fs.readFile('b.txt',(err,data) => {
if(data){
fs.readFile('c.txt',(err,data) => {
.....
})
}
})
}
})
这种方式算是js,最初解决异步的的方式,从而延申了回调地狱这个问题。
一般出现的情况会有
1、ajax求情回调
2、定时器回调
3、事件回调
4、node中的一些方法回调
回调地狱的缺点也显而易见,嵌套层级太多话,会出现无法维护与修改、代码量比较多。
Promise
为了解决回调地狱,ES6的时候引入了promise的方式。这个promise算是解决了回调地狱的嵌套层级太多,带来的问题
function readFileFun(url) {
return new Promise((resolve,reject) => {
fs.readFile(url,function(err,data){
if(err) reject(err)
resolve(data)
})
})
}
readFileFun('a.txt').then(res=>{
return readFileFun('b.text')
}).then(res=> {
return readFileFun('c.txt')
}).catch(err => {
console.log(err)
})
这个代码就一目了然了,通过promise的来链式调用,解决了嵌套层级的问题,代码可读性提高了。别且用同步的形式把异步的操作表达了出来。promise还有是一些问题的。就是链式调用解决了嵌套层级多,没有从根本上解决,只是换了一种形式,代码可读性好了。promise也有一个all的方法来可以解决链式调用。
function readFileFun(url) {
return new Promise((resolve,reject) => {
fs.readFile(url,function(err,data){
if(err) reject(err)
resolve(data)
})
})
}
Promise.all([readFileFun('a.txt'),readFileFun('b.txt'),readFileFun('c.txt')]).then(data=> {
console.log(data)
}).catch(err=> {
console.log(err)
})
这种形式看起来比链式调用好多了,代码看起来也比较清晰。代码可读性更高了。
Generator
Generator也是一种异步解决的方案,最大的有点就是交出了函数的执行权,Generator可以看作是异步的执行器。暂停的时候通过yield来控制,Genetator函数配合yield来使用,别切返回的是一个迭代器,返回的形式一般都是[value:XXX,done:false]。当done为false的时候说明已经继续返回了。
function *gen() {
console.log('开始')
let a = yield 1
let c = 4
let b = yield (function(){return 2})()
return 3
}
let gfun = gen()
console.log(gfun,'==========') // Object [Generator] {} ==========
console.log(gfun.next()) // { value: 1, done: false }
console.log(gfun.next()) // { value: 2, done: false }
console.log(gfun.next()) // { value: 3, done: true }
没有使用yield的就没有出现打印 ,所以通过上边看得出,Generator的执行和返回。
async/await
async/await是继ES6之后ES7推出的方案,它是Generator的语法糖,并且代码可读性非常高,同时也解决了promise链式调用的问题。
function testWait() {
return new Promise((resolve,reject)=>{
setTimeout(function(){
console.log("testWait");
resolve();
}, 1000);
})
}
async function testAwaitUse(){
await testWait()
console.log("hello");
return 123;
// 输出顺序:testWait,hello
// 如果不使用await输出顺序:hello , testWait
}
console.log(testAwaitUse());
执行上面的代码,从结果中可以看出,在正常的执行顺序下,testWait 这个函数由于使用的是 setTimeout 的定时器,回调会在一秒之后执行,但是由于执行到这里采用了 await 关键词,testAwaitUse 函数在执行的过程中需要等待 testWait 函数执行完成之后,再执行打印 hello 的操作。但是如果去掉 await ,打印结果的顺序就会变化。