📔
饥人谷前端体系课程笔记
黑马程序员笔记前端面试押题前端精进
  • 课程大纲
  • Git入门
    • 3. 软件安装详解
    • 4. Mac环境搭建
    • 5. 命令行入门
    • 6. 本地仓库
    • 7. Git远程仓库-GitHub
  • HTML全解
    • 8. HTML概览
    • 9. HTML标签
    • 10. HTML重难点
    • 11. HTML实践 & 手机调试
  • CSS全解
    • 12. CSS基础
    • 13. CSS布局(上)
    • 14. CSS布局(下)
    • 15. CSS定位
    • 16. CSS动画
  • HTTP全解
    • 17. URL 是什么
    • 18. 请求和响应 & Node.js Server
  • JS全解
    • 19. JavaScript概览
    • 20. 内存图与JS世界(精品课)
    • 21. Canvas 实践—画图板
    • 22. JS语法
    • 23. JS数据类型
    • 24. JS 对象
    • 25. JS 对象分类
    • 26. JS 数组
    • 27. JS 函数
    • 28. JS 实战,会动的代码
    • 29. JS运算符
    • 30. JS总结
  • JS编程接口
    • 31. DOM编程
    • 32. 手写DOM库
    • 33. JQuery中的设计模式(上)
    • 34. JQuery中的设计模式(下)
    • 35. DOM事件与事件委托
  • 项目 前端导航站点
    • 前端导航项目笔记
  • 前后分离
    • 40. AJAX的原理
    • 41. 异步与Promise
    • 42. 跨域、CORS、JSONP
    • 43. 静态服务器
    • 44. AJAX实战:Cookie、Session
  • JS进阶MVC
    • 48. MVC(上)
    • 49. MVC(中)
    • 50. MVC(下)
  • 项目构建
    • 51. Webpack(上)
    • 52. Webpack(下)
  • 算法与数据结构
    • 62. 伪代码与流程图
  • React全解
    • 75. React起手式
    • 76. React类组件和函数组件
    • 77. Class组件详解
    • 78. 函数组件
    • 79. Hooks原理解析
    • 80. Hooks各个击破
    • 81. 精通Redux
  • Node.js
    • 84. Node.js技术架构
    • 85. 文件模块
    • 86. 单元测试之文件模块
    • 87. 调试 Node.js 程序
    • 88. 静态服务器
    • 89. 命令行翻译工具
    • 90. 操作数据库
    • 91. 数据库基础知识
    • 92. Stream 流
    • 93. child_process
    • 94. 总结
  • TypeScript
    • 112. 基础
    • 113. 泛型
  • Next.js全解
    • Next.js 上
    • Next.js 下
  • ES6精讲
    • Promise,async/await
  • 大屏可视化笔记
    • 大屏可视化项目
  • SCSS全解
    • SCSS全解
  • 拓展
    • 一次性弄懂性能优化
    • Web性能优化
由 GitBook 提供支持
在本页
  • 什么是同步?
  • 什么是异步?
  • 异步举例
  • 回调
  • 什么是回调?
  • 异步与回调的关系
  • 如何判断一个函数是同步还是异步
  • 如果异步任务有两个结果成功或失败,怎么办
  • jQuery.ajax
  • axios
  • axios 高级用法

这有帮助吗?

  1. 前后分离

41. 异步与Promise

Promise

什么是异步?什么是同步?

网上的解释经常混淆异步与回调

什么是同步?

如果能直接拿到结果就是同步。

举例:

比如你在医院挂号,你拿到号才会离开窗口。

同步任务可能消耗10毫秒,也可以需要3秒,总之不拿到结果你是不会离开的。

什么是异步?

如果不能直接拿到结果就是异步。

举例:

比如你在餐厅门口等位,你拿到号可以去逛街。

什么时候才能真正吃饭呢?

你可以每10分钟去餐厅问一下(轮询)。

你也可以扫码用微信接受通知。

异步举例

以AJAX为例

request.send()之后,并不能直接得到response,不信的话可以用console.log(request.response)试试。必须等到readyState变为4后,浏览器回头调用request.onReadystatechange函数,我们才能得到request.response,这跟餐厅给你发送微信提醒的过程是类似的。

回调callback

你写给自己用的函数,不是回调,你写给别人用的函数,就是回调。request.onreadystatechange就是我写给浏览器调用的意思就是你(浏览器)回头调一下这个函数。回头也有将来的意思,就是在将来的某一个时刻,调用下这个函数。

getJson.onclick = ()=>{
  const request = new XMLHttpRequest();
  request.open("get","/5.json");
  request.onreadystatechange = () => {
    if(request.readyState === 4 && request.status === 200){
      const bool = JSON.parse(request.response);
      console.log(request.response)
    }
  };
  request.send();
  console.log(request.response)
}

回调

什么是回调?

写了却不调用,给别人调用的函数,就是回调。(回头你调用一下呗)

回调举例1

把函数1给另一个函数2

function f1(){}
function f2(fn){
  fn()
}
f2(f1)

分析

我调用f1没有?答:没有

我把f1传给f2(别人)了没有?答:传了

f2调用f1了没有?f2调用了f1

那么,f1是不是我写给f2调用的函数,答:是

所以,f1是回调。

回调举例2

function f1(x){
  console.log(x)
}

function f2(fn){
  fn('你好')
}
f2(f1)

f1是回调函数,被传给f2,f1作为回调函数接受'你好'作为参数。

异步与回调的关系

关联

异步任务需要在得到结果时通知JS来拿结果,可以让JS写留一个函数地址(电话号码)给浏览器。异步任务完成时浏览器调用该函数地址即可(拨打电话)。同时把结果作为参数传给该函数(电话里说可以来吃了)。这个函数时我写给浏览器调用的,所以是回调函数。

区别

异步常常用到回调,但是不一定要用到回调。

异步任务需要用到回调函数来通知结果(也可以用到轮询)

调函数不一定只用在异步任务里

回调可以用到同步任务里,比如,array.forEach(n => console.log(n))就是同步回调。

如何判断一个函数是同步还是异步

很简单,跟据特征或者文档。

判断同步异步

如果一个函数的返回值处于下面三种情况,那么这个函数就是异步函数。

  • setTimeout

  • AJAX(即XMLHttpRequest)

  • AddEventListener

问:我听说AJAX可以设置为同步的

答:啥x前端才把AJAX设置为同步的,这样做会使请求期间页面卡住。

AJAX也可以做同步请求,只需要添加false即可。

getJson.onclick = ()=>{
  const request = new XMLHttpRequest();
  request.open("get","/5.json", false);
  request.onreadystatechange = () => {
    if(request.readyState === 4 && request.status === 200){
      const bool = JSON.parse(request.response);
      console.log(request.response)
    }
  };
  request.send();
  console.log(request.response)
}

request.open("get", "/5.json", false)

看下面图中绿色的bar,当把AJAX设置成同步之后,会等上一个请求完成才开始执行,同时页面也会出现卡顿。浏览器知道用户点击了,但是不会有任何反馈。

举例(什么样的代码是异步的?)

摇骰子

function 摇骰子(){
  setTimeout(()=>{
    return parseInt(Math.random() * 6) + 1
  }, 1000)
  //return undefined
}

分析

摇骰子()没有写return,那就是return undefined

箭头函数里有return,返回真正的结果,所以这是一个异步函数/异步任务。

const n = 摇骰子()
console.log(n) //undefined

如何拿到异步结果?

答:可以用回调。写个函数,然后把函数地址给它。然后,要求摇骰子函数得到结果后把结果作为参数传给f1。

function f1(x){
	console.log(x)
}
function 摇骰子(fn){
	setTimeout(()=>{
		fn(parseInt(Math.random() * 6) + 1)
	},1000)
}

简化为箭头函数

由于f1声明之后只用了一次,所以可以删掉f1

function f1(x){console.log(x)}
摇骰子(f1)
//改为
摇骰子(x => {
  console.log(x)
})//等于省了一个变量
//再简化为
摇骰子(console.log)
//如果参数个数不一致就不能这样简化,有个面试题如下

著名的面试题

const array = ['1', '2', '3'].map(parseInt)

console.log(array)

//[1, NaN, NaN]

//原因
const array = ['1', '2','3'].map((item, i, arr) => {
  return parseInt(item, i , arr)
  //parseInt('1', 0, arr) => 1
  //parseInt('2', 1, arr) => NaN 这里的1代表1进制
  //parseInt('3', 2, arr) => NaN
})
//必须写成下面这样
const array = ['1', '2', '3'].map(item => parseInt(item))
console.log(array)

⚠️注意:

摇骰子(console.log)
//这里的console.log后面没有括号,也就是说没有被主动调用,而是被摇骰子调用。

总结

异步任务不能拿到结果

于是我们传一个回调给异步任务

异步任务完成时调用回调

调用的时候把结果作为参数

希望你已经理解上面的过程。

如果异步任务有两个结果成功或失败,怎么办

两个结果

方法一:回调接受两个参数

fs.readFile(’./1.txt‘, (error,data) => {
	if(error){console.log('失败'); return }
	console.log(data.toString())//成功
})

方法二:搞两个回调

ajax('get', '/1.json',data =>{}, error => {})
//前面函数是成功回调,后面函数是失败回调
ajax('get','/1.json',{
  success:()=>{},fail: ()=>{}
})
//接受一个对象,对象有两个key表示成功和失败

这些方法的不足

不管方法一还是方法二,都有问题

面试官会问,为什么要用到promise?以下就是答案

  1. 不规范,名称五花八门,有人用success + error, 有人用success + fail, 有人用done + fail

  2. 容易出现回调地狱,代码变得看不懂

  3. 很难进行错误处理

回调地狱举例

getUser( user => {
  getGroups(user,(groups) => {
    groups.forEach( (g) => {
      g.filter( x => x.ownerId === user.id)
      .forEach( x => console.log(x))
    })
  })
})

这还只是四层回调,你能想象20层回调吗?

怎么解决回调问题

有什么办法能解决这三个问题

  • 规范回调的名字或顺序

  • 拒绝回调地狱,让代码可读性更强

  • 很方便地捕获错误

前端程序员开始翻书了

1976年,Daniel P. Friedman和David Wise两人提出了Promise思想。后人基于此发明了Future,Delay,Deferred等。

前端结合Promise和JS,制定了Promise/A+规范。该规范详细描述了Promise的原理和使用方法。

以AJAX的封装为例来解释Promise的用法

ajax = (method, url, options) => {
  const {success, fail} = options;
  const request = new XMLHttpRequest()
  request.open(method,url)
  request.onreadystatechange = () => {
    if(request.status < 400){
      success.call(null, request.response)
    }else if(request.status >= 400){
      fail.call(null,request,request.status)
    }
  }
}
request.send()
}

ajax('get','/xxx',{
  success(response){},fail:(request,status) => {}
})
//success(response){} 是function的缩写,正常写法是success:function response(){}
//fail:(request,status)=>{} 右边是箭头函数
const {success, fail} = options //析构赋值
//如果不用上面的写法,那么原始的写法如下
const success = options.success
const fail = options.fail

Promise说这代码太傻了,我们改成Promise写法

//先改一下调用的姿势
ajax('get','/xxx',{
	success(response){},fail:(request,status) => {}
})
//上面用到了两个回调,还使用了success和fail
//改成Promise写法
ajax('get','/xxx').then((response) => {}, (request) => {})
/* 虽然也是回调
但是不需要记success和fail了
then的第一个参数就是success
then的第二个参数就是fail

请问ajax()返回了个啥
返回了一个含有.then()方法的对象呗
那么再请问如何得到这个含有.then()的对象呢
那就要改造ajax的源码了

*/

完整代码

ajax = (method, url, options) => {
  return new Promise((resolve, reject) => {
    const {success,fail} = options
    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.send()
  })
}

return new Promise((resolve, reject) =>{})背下这五个单词即可,等你用熟了。。。

小结

  • 第一步

    return new Promise ((resolve,reject)=>{...})

    任务成功则调用 resolve(result)

    任务失败则调用 reject(error)

    resolve 和reject 会再去调用成功和失败函数

  • 第二步

    使用.then(success, fail) 传入成功和失败函数

  • 点到为止

    先讲到这里,Promise 还有高级用法,以后说

我们封装的 ajax 的缺点

  • post 无法上传数据

    request.send(这里可以上传数据)

  • 不能设置请求头

    request.setRequestHeader(key, value)

  • 怎么解决呢?

    花时间把 ajax 写到完美(有时间可以做)

    使用 jQuery.ajax(这个可以)

    使用 axios(这个库比 jQuery逼格高)

jQuery.ajax

  • 已经非常完美

    进入jQuery 的文档,搜索 ajax,找到jQuery.ajax

    看看参数说明,然后直接看代码示例

    看看jQuery 的封装,就知道自己的封装是辣鸡了

  • 封装优点

    支持更多形式的参数

    支持 Promise

    支持的功能超多

  • 我们需要掌握 jQuery.ajax 吗?

    不用,现在的专业前端都在用 axios

    写篇博客罗列一下功能,就可以忘掉jQuery了

axios

  • 目前最新的 AJAX库

    显然它抄袭了 jQuery 的封装思路

    方方记得 axios 的API 吗?

    通过这个博客我们可以快速了解 axios 的用法

    推荐大家也可以通过写博客来学习一个库

  • 代码示例

    axios.get('/5.ison')
    
    	.then( response =>
    
    console.log (response)
            
    )        

axios 高级用法

  • JSON 自动处理

    axios 如何发现响应的 Content-Type 是json

    就会自动调用 JSON.parse

    所以说正确设置 Content-Type 是好习惯

  • 请求拦截器

    你可以在所有请求里加些东西,比如加查询参数.

  • 响应拦截器

    你可以在所有响应里加些东西,甚至改内容

    比如说发一个请求,但是后端的api还没有写好,就可以通过响应拦截器来篡改

  • 可以生成不同实例(对象)

    不同的实例可以设置不同的配置,用于复杂场景

封装!封装!封装! 初级程序员学习 API(包括 vue / React 的 API) 中级程序员学习如何封装 高级程序员造轮子

课后作业

Promise问答题

你需要自行查看Promise的MDN文档才能答对

Axios问答题

你需要自行查看axios的文档才能答对

我们的项目会经常用到这俩玩意

所以忘了也没关系

总结

Promise的最大缺点就是不可以取消,所以axios发明了axios.CancelToken,本质就是编个号。

  • 异步是什么?

    • 不能直接拿到结果的就叫异步

  • 异步为什么会用到回调?

    • 需要用回调拿到不能直接拿到的结果

  • 回调有哪三个问题?

    • 回调地狱,名字不规范,错误处理

  • Promise是什么?

    • 1976年的一种设计模式

  • 如何使用Promise?背下来五个词

    • return new Promise((resolve,reject)=>{})

  • 如果使用Axios:发个请求试用看看

  • Promise是前端解决异步问题的统一方案

上一页40. AJAX的原理下一页42. 跨域、CORS、JSONP

最后更新于2年前

这有帮助吗?

截屏2023-04-16 下午6.30.45
Callback Functions & Callback Hell - DEV Community

中文文档:

不记得,但是我写了

地址
博客
链接