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()

    1. request.send()之后并不能直接得到request.response
    2. request.onreadystatechange(callback)就是异步代码,需要readystate改变时,调用里面的callback
    3. 而这个时候我们才可以得到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
    这三个东西内部,那么这个函数就是异步函数

    例:摇骰子函数

    1. function f2(){
    2. setTimeout(()=>{
    3. return parseInt(Math.random()*6,10)+1
    4. },1000)
    5. }

    问:如何拿到异步结果?
    答:用回调。写个函数,然后把函数传给骰子函数,把结果传入回调函数

    1. function f1(x){console.log(x)}
    2. //结果:parseInt(Math.random()*6,10)+1
    3. //回调函数: f1
    4. function f2(fn){
    5. setTimeout(()=>{
    6. fn(parseInt(Math.random()*6,10)+1)
    7. },1000)
    8. }
    9. f2(f1)
    10. //调用摇骰子函数,把回调函数f1当做参数传入
    11. //摇骰子函数的输出结果就是1~6了
    1. //由于f1函数声明过后只用了一次,因此可以简化成箭头函数,节省变量声明
    2. function f2(fn){
    3. setTimeout(()=>{
    4. fn(parseInt(Math.random()*6,10)+1)
    5. },1000)
    6. }
    7. //调用时直接
    8. f2(x=>console.log(x))
    9. //如果回调函数的参数个数一致就能进一步简化
    10. f2(console.log)
    11. //这样写就表示,f2给f1传多少参数,f1就拿多少参数。
    1. //面试题:
    2. const array = ['1','2','3'].map(parseInt())
    3. console.log(array)
    4. /*问:输出结果是什么?为什么?
    5. 答:输出结果是 [1,NaN,NaN],
    6. map函数会得到三个结果,上述代码里map会将这三个结果作为参数传入到匿名回调函数里,
    7. 回调函数再把这三个参数传入它里面调用的parseInt()函数
    8. 上述简化代码可以还原为*/
    9. const array = ['1','2','3'].map((item,index,arr)=>{ return parseInt(item,index,arr)})
    10. /*因为parseInt函数只接受两个参数string和radix,所以会自动忽略map传的第三个函数
    11. 对应起来就是item传到string位置,index传到radix位置
    12. 会得到parseInt("1",0) === 1 ,当传入的radix=0 ,相当于不传
    13. parseInt("2",1) === NaN radix = 1 按照一进制解析 "2",将其转为十进制数
    14. parseInt("3",2) === NaN radix = 2 按照二进制解析 "3",将其转为十进制数
    15. 所以最后得到的结果 array = [1,NaN,NaN]*/
    16. //正确的写法是
    17. const array = ['1','2','3'].map((item)=>parseInt(item))

    异步总结:
    异步任务不能拿到结果
    于是我们传一个回调函数给异步任务
    异步任务完成时调用回调
    调用的时候把结果作为参数

    1. //如果异步任务有两个结果,成功和失败需要怎么做
    2. //方法一:回调函数接收两个参数
    3. fs.readFile(./1.txt,(error,data)=>{
    4. if(error){console.log("失败")
    5. }else{console.log(data.toString())
    6. }
    7. })
    8. //方法二:写两个回调函数
    9. ajax('get','/1.json',data=>{},error=>{})
    10. //或
    11. ajax('get','/1.json',{success:()=>{},fail:()=>{}})//接收一个对象,有两个key表示成功和失败
    12. /*
    13. 方法一和二都存在问题:
    14. 不规范,关于成功和失败的用词和位置五花八门
    15. 容易出现回调地狱
    16. 很难进行错误处理
    17. 该怎么处理这些问题:
    18. 规范回调的名字和顺序
    19. 拒绝回调地狱,让代码的可读性更强
    20. 很方便捕捉错误
    21. */
    1. //以ajax的封装为例
    2. ajax = (method ,url ,options) =>{
    3. const {success,fail} = options //解构赋值
    4. const request = new XMLHttpRequest()
    5. request.open(method,url)
    6. request.onreadystatechange = ()=>{
    7. if(
    8. request.readyState === 4 &&
    9. request.status >= 200 &&
    10. request.status < 400
    11. ){
    12. success.call(null,request.response)
    13. }else if(request.readyState === 4 &&
    14. request.status >= 400){
    15. fail.call(null,request,request.status)
    16. }
    17. }
    18. request.send()
    19. }
    20. ajax('get','/xxx',{success(response){},fail:(resquest,status)=>{}
    21. })//success是function缩写,fail是箭头函数
    22. //但是promise觉得这ajax封装得太傻了
    23. //先更改调用方式:原来的用了两个回调函数,还是用了success和fail
    24. //改成promise思想封装ajax的写法:
    25. ajax('get','/xxx').then((response)=>{},(request,status)=>{})
    26. //方法then的两个参数就是回调函数,两个回调函数接收的参数就是从异步操作那传来的结果
    27. 成功时执行的回调函数接收一个最终结果作为参数
    28. 失败时执行的回调函数接收一个拒绝原因作为参数
    29. /*
    30. 虽然该方法依旧是回调
    31. 但是不需要即success和fail了
    32. then的第一个参数就是success
    33. 第二个参数就是fail
    34. 通过改写ajax封装让ajax()返回一个含有then()方法的对象
    35. */
    36. ajax = (method,url)=>{
    37. return new Promise((resolve,reject)=>{
    38. const request = new XMLHttpRequest()
    39. request.open(method,url)
    40. request.onreadystatechange = ()=>{
    41. if(request.readyState === 4){
    42. if(request.status < 400){
    43. resolve.call(null,request.response)
    44. }else if(request.status >= 400){
    45. reject.call(null,request,request.status)
    46. }
    47. }
    48. }
    49. request.send()
    50. })
    51. }
    52. /*
    53. 第一步:return new Promise((resolve,reject)=>{...})
    54. 任务成功则调用resolve(result)
    55. 失败则调用reject(error)
    56. resolve和reject分别会再去调用成功和失败的函数
    57. 第二步:使用.then(success,fail)传入成功和失败函数
    58. */