JavaScript一般来说是单线程的(single threaded),此线程称为主线程(main thread)
一个线程是一个基本的处理过程,程序用它来完成任务。每个线程一次只能执行一个任务
同步代码:只有当一段代码块(或一个任务)完成后才能执行下一段代码块(或任务),这些代码称为同步代码
异步代码:JS通过 Web workers 可以把任务交给名为worker的单独线程,交给worker的代码就称为异步代码
同步代码的执行有可能会导致阻塞,但异步代码在执行的过程中就不会阻塞主线程
Web workers 并不能直接访问DOM——不能直接让一个worker直接更新UI
当一个函数依赖在它之前执行的异步代码的结果,需要如何解决?
- 起初的解决方案是使用回调函数(callback)
- 但容易出现回调地狱
- 且不容易处理错误
- 后来Promise的诞生不仅处理了异步代码结果问题,还避免了使用callback出现的弊端
以ajax为例
ajax就是异步操作JS向服务器发送请求并获取响应const request = new XMLHttpRequest()
- request.send()之后并不能直接得到request.response
- request.onreadystatechange(callback)就是异步代码,需要readystate改变时,调用里面的callback
- 而这个时候我们才可以得到request.response,并在callback中操作它
回调callback
写一个f1函数,传给f2函数当参数,让f2函数调用
function f1(){} 声明f1函数
function f2(fn){ fn() } 声明f2函数
function f2(f1) 调用f2函数,将f1作为参数传入f2函数,让f2函数调用它
所以f1就是回调
异步和回调的关系
关联:
异步任务在得到结果会通知JS
通过JS留给浏览器的的回调函数,把结果作为参数传给回调函数
区别:异步任务需要用到回调函数来通知结果
但函数回调不一定只用在异步任务里,回调函数同样可以用于同步任务
array.forEach(n=>console.log(n))就是同步回调
判断同步异步
如果一个函数的返回值处于
setTimeout
AJAX(即XMLHttpRequest)
addEventListener
这三个东西内部,那么这个函数就是异步函数
例:摇骰子函数
function f2(){setTimeout(()=>{return parseInt(Math.random()*6,10)+1},1000)}
问:如何拿到异步结果?
答:用回调。写个函数,然后把函数传给骰子函数,把结果传入回调函数
function f1(x){console.log(x)}//结果:parseInt(Math.random()*6,10)+1//回调函数: f1function f2(fn){setTimeout(()=>{fn(parseInt(Math.random()*6,10)+1)},1000)}f2(f1)//调用摇骰子函数,把回调函数f1当做参数传入//摇骰子函数的输出结果就是1~6了
//由于f1函数声明过后只用了一次,因此可以简化成箭头函数,节省变量声明function f2(fn){setTimeout(()=>{fn(parseInt(Math.random()*6,10)+1)},1000)}//调用时直接f2(x=>console.log(x))//如果回调函数的参数个数一致就能进一步简化f2(console.log)//这样写就表示,f2给f1传多少参数,f1就拿多少参数。
//面试题:const array = ['1','2','3'].map(parseInt())console.log(array)/*问:输出结果是什么?为什么?答:输出结果是 [1,NaN,NaN],map函数会得到三个结果,上述代码里map会将这三个结果作为参数传入到匿名回调函数里,回调函数再把这三个参数传入它里面调用的parseInt()函数上述简化代码可以还原为*/const array = ['1','2','3'].map((item,index,arr)=>{ return parseInt(item,index,arr)})/*因为parseInt函数只接受两个参数string和radix,所以会自动忽略map传的第三个函数对应起来就是item传到string位置,index传到radix位置会得到parseInt("1",0) === 1 ,当传入的radix=0 ,相当于不传parseInt("2",1) === NaN radix = 1 按照一进制解析 "2",将其转为十进制数parseInt("3",2) === NaN radix = 2 按照二进制解析 "3",将其转为十进制数所以最后得到的结果 array = [1,NaN,NaN]*///正确的写法是const array = ['1','2','3'].map((item)=>parseInt(item))
异步总结:
异步任务不能拿到结果
于是我们传一个回调函数给异步任务
异步任务完成时调用回调
调用的时候把结果作为参数
//如果异步任务有两个结果,成功和失败需要怎么做//方法一:回调函数接收两个参数fs.readFile(./1.txt,(error,data)=>{if(error){console.log("失败")}else{console.log(data.toString())}})//方法二:写两个回调函数ajax('get','/1.json',data=>{},error=>{})//或ajax('get','/1.json',{success:()=>{},fail:()=>{}})//接收一个对象,有两个key表示成功和失败/*方法一和二都存在问题:不规范,关于成功和失败的用词和位置五花八门容易出现回调地狱很难进行错误处理该怎么处理这些问题:规范回调的名字和顺序拒绝回调地狱,让代码的可读性更强很方便捕捉错误*/
//以ajax的封装为例ajax = (method ,url ,options) =>{const {success,fail} = options //解构赋值const request = new XMLHttpRequest()request.open(method,url)request.onreadystatechange = ()=>{if(request.readyState === 4 &&request.status >= 200 &&request.status < 400){success.call(null,request.response)}else if(request.readyState === 4 &&request.status >= 400){fail.call(null,request,request.status)}}request.send()}ajax('get','/xxx',{success(response){},fail:(resquest,status)=>{}})//success是function缩写,fail是箭头函数//但是promise觉得这ajax封装得太傻了//先更改调用方式:原来的用了两个回调函数,还是用了success和fail//改成promise思想封装ajax的写法:ajax('get','/xxx').then((response)=>{},(request,status)=>{})//方法then的两个参数就是回调函数,两个回调函数接收的参数就是从异步操作那传来的结果成功时执行的回调函数接收一个最终结果作为参数失败时执行的回调函数接收一个拒绝原因作为参数/*虽然该方法依旧是回调但是不需要即success和fail了then的第一个参数就是success第二个参数就是fail通过改写ajax封装让ajax()返回一个含有then()方法的对象*/ajax = (method,url)=>{return new Promise((resolve,reject)=>{const request = new XMLHttpRequest()request.open(method,url)request.onreadystatechange = ()=>{if(request.readyState === 4){if(request.status < 400){resolve.call(null,request.response)}else if(request.status >= 400){reject.call(null,request,request.status)}}}request.send()})}/*第一步:return new Promise((resolve,reject)=>{...})任务成功则调用resolve(result)失败则调用reject(error)resolve和reject分别会再去调用成功和失败的函数第二步:使用.then(success,fail)传入成功和失败函数*/
