回调函数的嵌套
回调函数
- 概念:
=> 把 函数A 当做实参, 传递到 函数B 内
=> 在 函数B 内用形参来调用 函数A
=> 这个行为我们叫做回调函数
=> 我们把 函数A 叫做 函数B 的回调函数 - 原因:
=> 因为 JS 是单线程(同一个时间只能做一个事情)
=> 当我需要在一个异步的末尾做一些事情, 没有办法控制 - 意义:
=> 在 封装 异步代码的时候
=> 需要在 异步的末尾 做一些事情
=> 使用回调函数的形式进行封装
-
回调函数的缺点
-
回调地狱
-
当回调函数嵌套过多的时候, 会出现回调地狱
-
导致代码的可维护性和可阅读性降低
-
// 封装一个函数, 用定时器模拟
function maibaozhi(jinnang = () => {}) {
const time = Math.floor(Math.random() * 4000 + 1000)
// // 定时器: 不固定 1 ~ 5 秒之间执行定时器函数
setTimeout(() => {
console.log('我买完报纸了, 本次用时 : ', time)
// 把你该做的事情做完了, 然后按照 jinnang 执行
jinnang()
}, time)
}
// // 让班长第一次买报纸, 在帮我买一瓶水
maibaozhi(function () { console.log('买水') })
// // 让班长第二次买报纸, 在帮我买一个面包
maibaozhi(function () { console.log('买面包') })
// 让班长第三次买报纸
maibaozhi()
常见的典型异步回调
setTimeout(function(){
console.log('1s之后做的事情');
setTimeout(function(){
console.log('2s之后做的事情');
setTimeout(function(){
console.log('3s之后做的事情');
},1000);
},1000);
},1000);
回调地狱
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lUnebM0Z-1663213523325)(/1567134159892.png)]
在回调函数中嵌套,就是上一个函数执行完成,再执行下一个
promise使用(掌握)
在JavaScript的世界中,所有代码都是单线程执行的。由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现.
div.onclick = function(){}
Promise使ES6中引入的一个异步编程解决方案,与传统的ES5方法相比它的结构更合理,功能更强大.
特点
Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态.
语法:
var p = new Promise(function(resolve,reject){})
resolve 代表 决定(成功); reject 代表 失败
使用
基本使用
const p = new Promise(function(resolve,reject){
setTimeout(function(){
// resolve('我是成功的')
reject('这是失败的');
},2000)
});
.then(callback)的使用(成功时调用)
.catch(callback的用法(失败时调用)
p.then(function(data){
console.log('resolved成功回调');
console.log('成功回调接受的值:',data);
}).catch(function(reason, data){
console.log('catch到rejected失败回调');
console.log('catch失败执行回调抛出失败原因:',reason);
});
效果和写在then的第二个参数里面一样。它将大于10的情况下的失败回调的原因输出
注意:resolve和reject的两种状态
resolve作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
reject作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
工作中常用方式,结合函数
我们用Promise的时候一般是包在一个函数中,在需要的时候去运行这个函数
let promiseFn =()=>{
console.log('点击方法被调用')
let p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('执行完成Promise');
resolve('成功的时候调用');
}, 2000);
});
return p
}
promiseFn().then(function(data){
console.log(11111)
}).catch(function(data){
console.log(2222)
})
静态方法的使用(掌握 )
promise并非一开始就必须处于待定的状态,然后通过执行器函数才能转化为成功或失败.也可以直接调用resolve和reject方法.
Primise.resolve()
let p1 = new Promise((resolve,reject)=> resolve());
let p2 = Promise.resolve()
// 两种方式结果是一样的.都表示状态切换到成功
Primise.reject()
let p1 = new Promise((resolve,reject)=> reject());
let p2 = Promise.reject()
// 都表示状态切换到失败
Primise.finally()
该方法无论期约为成功或者失败都可以调用,主要是表面then和catch当中出现冗余的代码.但它无法知道状态是成功还是失败,因此该方法主要用于添加清理代码.
let p1 = Promise.resolve();
let p2 =Promise.reject(); // 错误没有被处理 Uncaught (in promise)
p1.finally(function(){
console.log('被调用...');
})
p2.finally(function(){
console.log('可以...');
})
promise改造ajax依赖调用
使用promise检测ajax的请求状态,利用promise的成功状态函数,返回ajax请求到的数据
const ajaxParam=param=>{
return new Promise(function(resolve,reject){
let xhr = new XMLHttpRequest();
xhr.open('get',param.url); // 默认是true,表示异步
xhr.send();
xhr.onreadystatechange = function(){
if(xhr.readyState==4 ){
if(xhr.status==200){
resolve(xhr.responseText)
}else{
reject(xhr.responseText)
}
}
}
});
}
期约的连锁和合成(了解)
基本形式
每个期约的实例方法(then(),catch(),finally())都会返回一个新的期约对象,而这些期约对象又有自己的实例方法,如此连起来就是’期约连锁’
let p = new Promise((resolve,reject)=>{
console.log('first');
resolve();
});
p.then(()=>console.log('second'))
.then(()=>console.log('third'))
.then(()=>console.log('forth'))
以上执行,四个同步的任务,
串行调用
let p = new Promise((resolve,reject)=>{
//调用失败
reject();
// 调用resolve() 则不能在处理结果后调用catch
});
// catch() 处理错误,返回的promise,可以再调用then()
p.catch(()=>console.log('reject handler'))
.then(()=>console.log('resolve handler'))
.finally(()=>console.log('finally handler'))
期约图
一个期约可以有任意多个处理程序,一个处理程序就是一个节点,每一个节点都会等待前一个节点的落定,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8wdRtQVZ-1663213523326)(/1649060457753.png)]
let A = new Promise((resolve,reject)=>{
console.log('A');
resolve();
});
let B=A.then(()=>console.log('B'));
let C=A.then(()=>console.log('C'));
B.then(()=>console.log('D'));
B.then(()=>console.log('E'));
C.then(()=>console.log('F'));
C.then(()=>console.log('G'));
结合ajax使用
当第一次发送的网络请求得到的数据,为第二次请求所必须的数据,那么此时我们就要串联使用了.
-
发送请求, 请求测试接口1, 结果打印在控制台
-
发送请求, 请求测试接口2, 结果打印在控制台
-
发送请求, 请求测试接口3, 结果打印在控制台
=> 前提: 必须要在第二个请求完成以后, 在发送第三个请求
Promise 链式调用
- 当你在第一个 then 内部返回一个新的 Promise 对象的时候
可以把新的 Promise 的 then 连续书写
pAjax({ url: 'http://localhost:8888/test/first' })
.then(res => {
console.log(res)
// 这里是第一个请求完成后会执行的代码
// 调用 pAjax, 创建的第二个 承若(Promise)
// 在第一个 then 里面把新的 Promise return 了
return pAjax({ url: 'http://localhost:8888/test/second', dataType: 'json' })
})
.then(res2 => {
console.log(res2)
// 这里是第二个请求完成后会执行的代码
// 调用 pAjax, 创建第三个 承诺(Promise)
// 在第二个 then 里面被写的 Promise return 了
return pAjax({ url: 'http://localhost:8888/test/third', data: 'name=Jack&age=20', dataType: 'json' })
})
.then(res3 => console.log(res3))
期约的合并
Promise.all()
所有的异步请求完成才执行的方法.
Promse.all在处理多个异步处理时非常有用,比如说一个页面上需要等两个或多个ajax的数据回来以后才正常显示,在此之前只显示loading图标。
let wake = (time) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`${time / 1000}秒后醒来`)
}, time)
})
}
let p1 = wake(3000);
let p2 = wake(2000);
Promise.all([p1, p2]).then((result) => {
console.log(result) // [ '3秒后醒来', '2秒后醒来' ]
}).catch((error) => {
console.log(error)
})
只有两次调用promise都执行完毕,才会执行all
Promise.race()
Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
},1000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('failed')
}, 500)
})
Promise.race([p1, p2]).then((result) => {
console.log(result)
}).catch((error) => {
console.log(error) // 打开的是 'failed'
})
期约的错误处理
同步代码的错误
try…catch…只能捕获同步代码的错误,而对于异步它们是无能无力的
try{
throw new Error('错误抛出');
}catch(error){
console.log(error);
}
上下不要一起运行
try{
Promise.reject( new Error('错误抛出') )
}catch(error){
console.log(error);
}
这里的同步代码之所以没有捕获期约抛出的错误,是因为它们没有通过异步模式捕获错误.
期约既能在同步代码中使用,但也是异步执行的媒介.
期约错误的处理
抛出错误会被catch直接获取
let A = new Promise((resolve,reject)=>{
resolve();
});
A.then(()=>{
throw new Error('then中的错误')
}).catch(info=>{
console.log(info);
})
async 和await的使用(重点)
是什么
async 和 await 关键字
+ 只是对 Promise 语法的一种增强书写方式
+ 注意: 作用, 为了把异步代码写的看起来像同步
async 关键字
+ 在函数前面书写
+ 作用: 为了在该函数内可以使用 await 关键字
await 关键字
+ await 后面等待的必须是一个 Promise 对象
+ 目的: 为了等到 后面的 Promise 执行完毕在继续执行下一行代码
+ 作用: 本来应该在 then 里面接受的结果, 可以直接定义变量接受了
+ 解析:当js运行遇到await关键字时,会记录在哪里暂停执行,等await右边的值可用时,js会想消息队列推送任务,这个任务恢复异步的执行.
怎么用
async function fn() {
console.log('start')
// 等着第一个请求
const r1 = await pAjax({ url: 'http://localhost:8888/test/first' })
console.log(r1)
// 如果上面的 请求没有结束, 那么这里的代码不会执行
// 这里的代码执行了, 一定是因为 await 等到了上面的请求结束
const r2 = await pAjax({ url: 'http://localhost:8888/test/second', dataType: 'json' })
console.log(r2)
const r3 = await pAjax({ url: 'http://localhost:8888/test/third', data: 'name=Jack&age=20', dataType: 'json' })
console.log(r3)
console.log('end')
}
ajax的同源策略
同源策略的基本概念
1995年,同源政策由 Netscape 公司引入浏览器。目前,所有浏览器都实行这个政策。
同源策略:最初,它的含义是指,A网页设置的 Cookie,B网页不能打开,除非这两个网页"同源"。所谓"同源"指的是"三个相同"。
协议相同
域名相同
端口相同
举例来说,这个网址http://www.example.com/dir/page.html
协议是http
,
域名是www.example.com
,端口是80
(默认端口可以省略)。它的同源情况如下。
http://www.example.com/dir2/other.html:同源
file:///F:/phpStudy/WWW/day01/04-demo/04.html 不同源(协议不同)
http://v2.www.example.com/dir/other.html:不同源(域名不同)
http://www.example.com:81/dir/other.html:不同源(端口不同)
同源策略的目的
同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。
同源策略的限制范围
随着互联网的发展,“同源策略”越来越严格,目前,如果非同源,以下三种行为都将收到限制。
1. Cookie、LocalStorage 和 IndexDB 无法读取。
2. DOM 无法获得。
3. AJAX 请求在浏览器端有跨域限制
虽然这些限制是很有必要的,但是也给我们日常开发带来不好的影响。比如实际开发过程中,往往都会把服务器端架设到一台甚至是一个集群的服务器中,把客户端页面放到另外一个单独的服务器。那么这时候就会出现不同源的情况,如果我们知道两个网站都是安全的话,我们是希望两个不同源的网站之间可以相互请求数据的。这就需要使用到**跨域** 。
jsonp跨域 json+script
什么是跨域
域:表示不同的域名即网址不一样,如:www.baidu.com想访问www.edrc.cn就是不行的.
现在有两个网址 第一个 192.168.0.1 第二个 192.168.0.2 那么如果要在第一个的ip下.通过ajax访问第二个域名也是不可以的.
这是w3c组织为了安全性,做出的限制
• Ajax技术由于受到浏览器的限制(为了安全考虑),该方法不允许跨域通信。
在两个不同域名之间互相,发ajax请求.
如何解决跨域
一个公司,有服务器a, 服务器b,如a服务器要引用b服务器上的图片
<img src=”http://www.b.com/a.jpg” />甚至这个图片的路径也可以是线上的.
因为src标签 是没有同源限制。可以借助与script标签中的src属性来实现跨域问题.
在server/src/app.js中修改添加配置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qrJSRpIo-1663213523328)(/1646289400339.png)]
app.use('/gx',(req,res)=>{
console.log(req.query.cb)
res.send(req.query.cb+'({"code":1,"message":"登录成功"})');
res.end();
})
1 使用script标签发送请求
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script>
function fn(data){
console.log(data)
}
//凡是通过地址栏传参的都是get方式
</script>
<script src="http://localhost:8888/gx?cb=fn"></script>
</body>
</html>
JOSNP跨域
json + 动态script
1 JSON可以传输大量数据。
2 script标签 是没有同源策略限制
思路
在需要用到外部跨域,再生成 script标签.
实际操作
script标签没有同源性问题,我们可以借助script标签中的src属性来实现跨域问题。为了灵活使用跨域,一般使用动态生成SCRIPT标签来引用外部JS来实现跨域。
JSONP是指客户端用GET方式传递一个callback参数给服务端,然后在服务端返回数据时将这个callback参数作为函数名来包裹住JSON数据来返回给客户端(生成JS代码),然后客户端,用JS动态生成script标签,并指定该script的src属性等于服务器返回的数据(服务器生成的JS代码),这样服务器端返回的数据,就可以成为脚本的一部分。并将该这样客户端就通过callback函数来处理返回数据了。
将上面的script标签,进行动态生成
面试题:说一说json与jsonp的一个区别?
答:json是一种通用的数据交换格式,主要实现数据的传输与储存。
jsonp是一种非官方协议,主要为了解决Ajax跨域请求问题
请求淘宝或百度搜索下拉提示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zqPjob1e-1663213523328)(/1563178861627.png)]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
#search {
width: 600px;
margin: 0 auto;
margin-top: 300px;
position: relative;
}
#search input {
width: 480px;
height: 100%;
border: 1px solid #b6b6b6;
height: 20px;
padding: 9px 7px;
font: 16px arial;
border: 1px solid #b8b8b8;
border-bottom: 1px solid #ccc;
border-right: 0;
vertical-align: top;
outline: none;
box-shadow: none;
-webkit-appearance: textfield;
background-color: white;
-webkit-rtl-ordering: logical;
user-select: text;
}
#search button {
cursor: pointer;
box-sizing: border-box;
width: 97px;
height: 40px;
line-height: 38px;
padding: 0;
border: 0;
background: none;
background-color: #38f;
font-size: 16px;
color: white;
box-shadow: none;
font-weight: normal;
margin-left: -20px;
}
.result {
position: absolute;
padding: 9px 7px;
background: #ddd;
}
.search-res {
position: absolute;
top: 100%;
left: 0;
width: 480px;
border: 1px solid #b6b6b6;
border-top: none;
}
.search-res li {
list-style-type: none;
line-height: 20px;
padding: 2px 5px;
border-bottom: 1px solid #b6b6b6;
}
</style>
<script>
window.onload = function () {
var oButton = $("#search").children[0];
var oText = $("#search").children[0];
var timerOut;
oText.oninput = function () {
//获取数据
//延时获取; >> 防止事件多次触发请求冗余过多;
getRes(oText.value);//获取数据;
}
}
function getRes(val) {
var script = document.createElement("script");
script.src = "https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=" + val + "&json=1&p=3&sid=22084_1436_13548_21120_22036_22073&req=2&csor=0&cb=callback";
document.getElementsByTagName("head")[0].appendChild(script);
}
function callback(res) {//回调函数;
console.log(res.s);//回调数组 中都是关键词字符串;
var str = "";
for (var i = 0; i < res.s.length; i++) {
str += "<li>" + res.s[i] + "</li>";
}
$(".search-res").innerHTML = str;
}
function $(select) {
return document.querySelector(select);
}
</script>
</head>
<body>
<div id="search">
<input type="text">
<button>嗖嗖嗖一下</button>
<ul class="search-res">
</ul>
</div>
</body>
</html>
ajax的兼容(了解)
现在一般最多兼容到 IE8, 这里以后见到了知道是在处理兼容性就行了
var xhr = null;
if(XMLHttpRequest){
//现代浏览器 IE7+
xhr = new XMLHttpRequest();
}else{
//老版本的 Internet Explorer (IE5 和 IE6)使用 ActiveX 对象:
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
}
作业:
1 掌握jsonp跨域原理
2 实现搜索下拉框的提示此词消失
“;
for (var i = 0; i < res.s.length; i++) {
str += “
- ” + res.s[i] + “
- ”;
}
$(”.search-res").innerHTML = str;
}
function $(select) {
return document.querySelector(select);
}嗖嗖嗖一下```</ul>
ajax的兼容(了解)
现在一般最多兼容到 IE8, 这里以后见到了知道是在处理兼容性就行了
var xhr = null; if(XMLHttpRequest){ //现代浏览器 IE7+ xhr = new XMLHttpRequest(); }else{ //老版本的 Internet Explorer (IE5 和 IE6)使用 ActiveX 对象: xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); }
作业:
1 掌握jsonp跨域原理
2 实现搜索下拉框的提示此词消失