前言
JS执行是单线程的,但是在JS中需要有大量进行查询、获取数据的操作,例如AJAX,如果都按照顺序执行,那么在用户体验等多个方面肯定是极差的。那么就衍生了一系列的异步操作。
callback
最简单,最早产生的异步解决方案就是callback
,常说的回调函数
,举个例子:
var A=function(){
// var aa= fs.readFileSync('test.txt',{encoding:'utf-8'})
// console.log(aa)
for(var i=0;i<999;i++){}
console.log('A')
}
var B=function(callback){
callback()
console.log('B')
}
B(A)
执行后结果如下:
A
B
这个感觉不对劲,不是说callback
是异步吗,为什么还是顺序执行呢。(需要注意的是callback
只是一种将异步编程同步化的解决方案而不是本身是异步的)其实callback函数仍然在主线程中执行,所以,调用callback,当前线程还是会去执行callback。网上有的解释并没有解释清楚。那callback
和异步操作
到底有什么关系呢?看下边个例子:
console.log('A')
$('button').on('click',function(){
console.log('success')
})
console.log('B')
执行结果如下:
A
B
为什么success没有被打印出来呢,应该学过JS
都知道把,因为它的打印是有条件的,需要点击的时候才能触发,总之就是可以通过这个条件来控制回调函数的执行顺序,现在可能有人还是会不太理解。
JS异步编程原理与回调函数
ES6之前,JavaScript中异步编程分为3类:DOM事件(如onclick)、网络请求(如ajax)、定时器(setTimeout/setInterval)。他们均使用回调函数来进行异步调用。
JS执行栈是单线程,但是JS的宿主环境不是单线程的(如浏览器、Node),浏览器内部是允许多个线程同时运行的,除JavaScript引擎线程外,还有事件触发线程、HTTP请求线程、定时器触发线程,他们和JavaScript线程是互不影响的,不会造成阻塞,JS执行的线程(即JavaScript引擎的所在线程)为主线程,浏览器会执行一个类似于while的轮询过程,每次循环查看线程中是否有待处理的任务,(如浏览器点击事件、AJAX请求、定时器等),如果存在则把该任务添加到JS主线程的执行任务列表中等待执行。
那么我们就很好明白了,诸如事件请求、定时器、事件点击等确实是异步的,但是不是由于JavaScript
本身的作用,JavaScript
本身依旧是单线程的,当碰到请求等事件时,浏览器会新开一个线程,当回调函数callback
调用的时候,那么之前的异步事件就产生了变更把回调函数放到JavaScript
引擎任务队列中等待执行。
尽管回调函数是一种异步解决方案,但正是异步操作的问题,会产生回调地狱的问题,如下
function async(){
setTimeout(function(){ //回调函数1
console.log(1);
setTimeout(function(){ //回调函数2
console.log(2);
setTimeout(function(){ //回调函数2
console.log(3);
},1000);
},1000);
},1000)
}
async();
//调用结果:1s后打印1 2s后打印2 3s后打印3
这是网上随便找的一个例子,我们可以看到一大堆的 {} 、(),看完脑袋有点疼,一旦嵌套层级变多,代码结构就变得很不乐观。
Promise
promise是ES6
中提出的一种异步解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6
将其写进了语言标准,统一了用法,原生提供了Promise
对象。其只有then这一个方法,then
方法带有两个参数:
- 成功回调
- 失败回调
promise对象有三个状态:
- pending(进行中,未完成)
- resolved(已完成)
- rejected(已失败)
(1)我们将上边的callback回调函数包装成Promise
function async1(data){
console.log(data)
var p=new Promise(function(resolve,reject){
setTimeout(function(){
resolve(2)
},1000)
})
return p
}
function async2(data){
console.log(data)
var p=new Promise(function(resolve,reject){
setTimeout(function(){
resolve(3)
},1000)
})
return p
}
(2)使用then
链式调用这三个方法
async()
.then(function(data){
return async1(data)
})
.then(function(data){
return async2(data)
})
.then(function(data){
console.log(data)
})
(3)还可以简化为
async()
.then((data)=> async1(data))
.then((data)=> async2(data))
.then((data)=>console.log(data))
//调用结果:1s后打印1 2s后打印2 3s后打印3
我们可以看到异步操作的结构清晰了许多,当然我们可以通过rejected
对异步操作进行错误,我们对函数进行错误处理。
function async(){
var p=new Promise(function(resolve,reject){
setTimeout(function(){
resolve(1)
},1000)
})
return p
}
function async1(data){
console.log(data)
var p=new Promise(function(resolve,reject){
setTimeout(function(){
reject(2)
},1000)
})
return p
}
function async2(data){
console.log(data)
var p=new Promise(function(resolve,reject){
setTimeout(function(){
resolve(3)
},1000)
})
return p
}
执行结果
1
(node:17952) UnhandledPromiseRejectionWarning: 2
(node:17952) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:17952) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
报错了,大概意思就是没对错误进行处理,使用catch
方法捕捉错误。
function async(){
var p=new Promise(function(resolve,reject){
setTimeout(function(){
reject('这是错误1')
},1000)
})
return p
}
function async1(data){
console.log('这是正确回调')
}
async()
.then(async1,(e)=>{console.log(e)})
//打印结果 这是错误1
我们还可以用
catch
方法来处理rejected
回调
async()
.then(async1)
.catch((data)=>console.log(data))
catch
方法的另一个作用是如果执行resolve
时代码出错(抛出错误),不会卡死出错,会进到catch
函数里边
await和async
async
异步操作返回一个Promise对象
可以使用then
链式调用
async function aa(){
return 123
}
console.log(aa())//Promise {<resolved>: 123}
console.log(aa().then(res=>console.log(res)))
//Promise {<pending>}
//123
await
字面意思就是等待
,在JavaScript
中意思和这个差不多,async
是异步操作,而await
就是等待
一个异步任务完成的结果。简单的说await
是一个操作符,async
是异步的方法。
await
只能用在async
中
async function demo(){
const data1= await async()
const data2=await async1()
console.log(data1,data2)
}
demo()
//打印结果 1 2
可以看到结果1和结果2同时打印,那么await
操作符的作用就比较明显了,await
就是将异步Promise
同步化的一种解决方案。不需要在then
链中进行回调操作。
例子
下边例子是使用Promis 和 原生 ajax 包装一个类似与fetch 的方法
var fetchOwn=function(url){
var data=new Promise((resolve,reject)=>{
var xml=new XMLHttpRequest()
xml.open('GET',url,true)
xml.send()
xml.onreadystatechange=function(){
if(xml.status==200&&xml.readyState==4){
resolve(xml.response)
}
}
})
return data
}
//解析json 为js对象
var fetchJson=function(json){
var data=new Promise((resolve,reject)=>{
resolve(JSON.parse(json))
})
return data
}
fetchOwn('http://jsonplaceholder.typicode.com/comments')
.then(e=>fetchJson(e))
.then(res=>console.log(res))
总结
JavaScript
本身仍然是单线程的,没有异步操作一说,但是其宿主环境
是多线程的问题,造成可以进行一些异步操作。callback
本身也不是异步操作的但是结合一些浏览器事件、HTTP请求等造成了callback回调异步操作的问题。callback
回调函数会造成地狱回调的问题,代码结构不清晰,Promise
对象很好的解决课这个问题。await
是操作符,是async同步化的一种方法。