一种封装代码的手段
什么是 callback , 概念
=> 把 函数A 当做 实参 传递到 函数B 内部
=> 在 函数B 内部以 形参 的方式 调用 函数A
=> 我们管这个行为叫做 回调函数
=> 我们说 函数A 是 函数B 的 回调函数
functionA(){ console.log('我是 A 函数')}functionB(fn){// 此时 fn 形参接受的是书写在 B() 的时候, () 内部的内容 : A// 此时 fn 形参接受的就是全局 函数 A 的地址// 此时 fn 形参和全局变量 A 操作一个函数空间 console.log('我是 B 函数')// 调用 fn 其实就是在调用执行全局的 A 函数fn()}// 调用 B 函数// A 是一个保存 函数的地址// 把 函数 A 这个地址当做实参传递给了 B 函数内部的 fn 形参B(A)// 函数A 是 函数B 的回调函数
为什么需要 callback 回调函数
如果从头到尾都是 同步代码, 不需要回调函数
=> 当你在 封装代码 的时候
=> 并且代码内有 异步 的时候
=> 并且需要在 异步的 末尾 做一些事情的时候
=> 使用 callback
解释: 为什么异步的末尾封装要使用 callback
因为 JS 的单线程,同一个时间点只能做一个事情
主要: 异步的结束时间不确定
例子: 外卖
=> 一个外卖员同一个时间点只能做一件事情
=> 如果你希望多带一双筷子
=> 方案1: 等到外卖员刚好到达店里的时候, 给他打电话
=> 方案2: 在点餐的时候给一个备注
回调函数的缺点:
回调地狱
当回调 嵌套 回调的时候, 代码的阅读和可维护性不高
解决回调地狱的问题:
Promise 来解决回调地狱
分析:
=> Promise 是来解决回调地狱
=> 回调地狱, 是因为回调函数嵌套过多
=> 回调函数, 为了解决在异步末尾做一些事情的封装
=> Promise 就是一种优雅的对于异步代码封装的方案
// 为什么需要回调函数// 封装一段代码// 例子 : 外卖公司做好的事情functionwaimai(beizhu){// 获取一个 1000 ~ 6000 的随机整数const time=1000* Math.round(Math.random()*5+1) console.log(' 在路上 '+ time)// 我们使用 setTimeout 模拟一个网络环境请求setTimeout(()=>{ console.log('到达店里了, 拿到外卖')// 直接把我需要执行的代码放在这个位置// 那么这个封装就没有意义了// 就需要用到回调函数了// 因为这个位置是异步的末尾了// 这个位置调用 beizhu 就是在异步的末尾调用// 例 : 不管什么时候到了店里// 拿到外卖以后, 把 备注 的内容执行一下beizhu()}, time)}// 用户的需求: 想多拿一双筷子waimai(function(){ console.log('多拿一双筷子')})// 用户的需求: 想多拿点辣椒waimai(function(){ console.log('多拿点辣椒')})
一种使用回调函数封装的代码时候的情况
回调函数的使用是有 函数嵌套 在里面的
当你大量使用回调函数封装的代码的时候, 会出现 结构紊乱
=> 不利于代码的阅读和维护
为了解决回调地狱
=> ES6 的语法内出现了一个新的语法, 叫做 Promise
=> 为了把 异步代码 封装变成 Promise 语法的封装
=> 不再使用 回调函数 来封装 异步代码了
=> 本质: 用来 封装异步代码 的
发送一个请求, 请求一个接口
=> 等到响应回来以后
=> 把内容打印在控制台
发送第二个请求, 请求第二个接口
=> 要求必须要在第一个请求结束以后, 打印完毕以后再次发送请求
=> 把响应内容打印在控制台
发送第三个请求, 请求第三个接口
=> 要求, 必须要在第二个请求结束以后, 打印完毕以后再次发送请求
=> 把响应内容打印在控制台
<script src="jquery.min.js"></script>// 实现需求 1 : $.ajax({ url:'http://localhost:8888/test/first',success:function(res){ console.log('第一次请求的结果') console.log(res)// 这个位置的代码执行的时候, 一定是第一个请求结束的时候// 需求2: $.ajax({ url:'http://localhost:8888/test/second', dataType:'json',success:function(res){ console.log('第二次请求的结果') console.log(res)// 这个位置的代码执行的时候, 一定是第二个请求结束的时候// 需求3: $.ajax({ url:'http://localhost:8888/test/third', data:'name=Jack&age=18', dataType:'json',success:function(res){ console.log('第三次请求的结果') console.log(res)}})}})}})
这里的ajax用的是jquery
是一个 ES6 出现的语法
Promise 也是一个 JS 内置的 构造函数
promise - 承诺 :
Promise 也有三个状态
=> 继续: pending
=> 成功: fulfilled
=> 失败: rejected
const p=newPromise(function(resolve, reject){// ...// ...})// 给当前这个承诺注册一个 成功以后的函数 p.then(function(){})// 给当前这个承诺注册一个 失败以后的函数 p.catch(function(){})
如何改变 promise 的状态
// 1. 异步代码const p=newPromise(function(resolve, reject){// resolve 就是一个转换成功的方法// 当你书写 resolve() 的时候, 就是在把 该 promise 的状态转换为成功// 就会执行 .then 时候里面书写的 b 函数// reject 就是一个转换成失败的方法// 当你书写 reject() 的时候, 就是在把 该 promise 的状态转换为失败// 就会执行 .catch 时候里面书写的 c 函数// 这两个只能书写一个// 书写你需要封装的异步代码const time=1000* Math.round(Math.random()*5+1) console.log('承诺一辈子在一起')setTimeout(()=>{if(time>=3000){// resolve() 调用的是 then 内部的函数 b// 所以这里书写在 () 内部的 time 内容就是给到 then 内 b 的实参resolve(time)}else{// reject() 调用的是 catch 内部的函数 c// 所以这里书写在 () 内部的 time 内容就是给到 catch 内 c 的实参, 也是报错信息reject(time)}}, time)})// promise 对象调用的两个方法// 注册 成功 p.then(functionb(t){// 函数 b 不会被直接调用的// 这个位置的代码会在 p 这个 promise 的状态由 继续 转换为 成功 的时候调用执行 console.log(t,'成功的函数 b')// t 就是你在 promise 内部书写的 resolve 的小括号里面 time 的内容})// 注册 失败 p.catch(functionc(err){// 函数 c 不会被直接调用// 这个位置的代码会在 p 这个 promise 的状态由 继续 转换为 失败 的时候调用执行 console.log(err,'失败的函数 c')})
当一个 Promise 的 then 内的代码
只要你在前一个 then 内部以 return 返回一个新的 promise 对象 的时候
新 promise 对象的 then 可以直接在前一个 then 的后面继续书写 then
需求:
发送一个请求, 请求第一个接口
发送第二个请求, 请求第二个接口
=> 前提: 必须要等到第一个请求结束以后再次发送
functionmyPromiseAjax(options={}){const p=newPromise((resolve, reject)=>{// 执行 ajax $.ajax({ url: options.url, data: options.data, type:options.type, dataType: options.dataType,success:function(res){resolve(res)}})})// 把我的 promise 对象返回出去return p}
myPromiseAjax({ url:'http://localhost:8888/test/first'}).then(res=>{ console.log('第一个请求结束了') console.log(res)// return 一个新的 promise 对象returnmyPromiseAjax({ url:'http://localhost:8888/test/second', dataType:'json'})}).then(res=>{ console.log('第二个请求结果') console.log(res)// return 一个新的 promise 对象returnmyPromiseAjax({ url:'http://localhost:8888/test/third', data:'name=Jack&age=20', dataType:'json'})}).then(res=>{ console.log('第三次请求的结果') console.log(res)})
ES7 ~ ES8 之间出现的语法
作用 :
为了解决 Promise 的问题 , 把 Promise 的代码书写的更优雅
核心作用: 把 异步代码 写的 看起来像 同步代码, 本质还是异步
语法:
=> async 关键字 (异步)
(可以是声明式函数, 可以是函数表达式, 可以是箭头函数)
// async 的语法asyncfunctionfn(){}constfn=asyncfunction(){}constfn=asynca=>{}
作用:
该函数内可以使用 await 关键字了
会把该函数变成一个 异步函数, 只是叫做 异步函数
(这个异步函数并不是我们真实的异步代码,只是给这个函数起了个名字)
=> 影响的是函数内部的代码 , 不影响函数外面的代码
await 关键字 (等待)
await 必须写在一个有 async 关键字的异步函数内部
await 后面等待的内容必须是一个 promise 对象 , 否则等不了
作用:
=> 把 promise 中本该在 then 内代码接受的结果 ,
可以直接在 await 前面定义变量接受
=> 后续的代码需要等到 promise 执行完毕才会执行
functionmyPromiseAjax(options={}){const p=newPromise((resolve, reject)=>{// 执行 ajax $.ajax({ url: options.url, data: options.data, type:options.type, dataType: options.dataType,success:function(res){resolve(res)}})})// 把我的 promise 对象返回出去return p}
console.log('start')// ① startasyncfunctionfn(){ console.log('我是 fn 函数内部的代码')// ②// 因为 myPromiseAjax是按照 promise 的语法形式进行封装的代码// myPromiseAjax会返回一个 promise 对象// fn 函数内, 执行到 myPromiseAjax这个代码的时候// 会等待, 等到这个异步的代码完全执行完毕, 把结果赋值给 r1 以后// 在继续执行后面的代码const r1=awaitmyPromiseAjax({ url:'http://localhost:8888/test/first'}) console.log(r1)// ④}fn() console.log('end')// ③ end
console.log('start')asyncfunctionfn(){ console.log('我是 fn 函数内部的代码')// 此时 fn 函数内可以使用 await 关键字了// myPromiseAjax返回出来的 promise 对象会执行// 把 resolve() 的时候 括号里面的内容 赋值给 r1. 在继续向后执行代码const r1=awaitmyPromiseAjax({ url:'http://localhost:8888/test/first'}) console.log(r1)// // 需求2:const r2=awaitmyPromiseAjax({ url:'http://localhost:8888/test/second', dataType:'json'}) console.log(r2)// 需求3:const r3=awaitmyPromiseAjax({ url:'http://localhost:8888/test/third', data:'name=Jack&age=20', dataType:'json'}) console.log(r3)}fn() console.log('end')