Ajax,axios,promise,formData学习笔记

服务器相关概念

1. 服务器

简而言之就是在网上提供服务计算机,它的本质就是网络上的一台配置非常高的电脑。 我们是看不到的,但我们经常使用它为我们提供的服务,比如: ​ 微信 ​ 网易云音乐 ​ 浏览器

2. 客户端(浏览器端)

简单理解就是我们的个人电脑。个人电脑去访问服务器提供的服务。

3. 通讯过程

以咱们用的最多的浏览器为例,和服务器通讯的过程就像聊微信?

  1. 我:---> 你好?在吗?欠我的钱,什么时候还?
  2. 他:--->不在!不还!

:point_right: 一次通讯有两部分组成:请求响应

5. url地址格式及作用

  1. url 格式
    • 协议名

      • http:
      • https:
      • ftp:
    • 主机名

      • ip地址

        网络中电脑的唯一标识

      • 域名:为了方便记忆,例如: www.baidu.com

        最终也要转换成ip地址

    • 端口号

      计算机安装的软件在进行网络通讯时的标识。

    • 请求信息

      http://www.thinker.com:8080/请求信息 端口号后面的即为请求信息,具有如下含意:

      • 请求服务器上的什么文件,
      • 本次请求给向服务器携带了什么样的数据,
      • 本次请求有什么目的
      • 等等......
  • AJAX 概念

    认识了服务器之后,咱们来认识以下ajax,并且体验以下他能够实现的效果

    概念

    AJAX 是异步的 JavaScript 和 XML(Asynchronous JavaScript And XML)。简单点说,就是使用 XMLHttpRequest 对象与服务器通信。 它可以使用 JSON,XML,HTML 和 text 文本等格式发送和接收数据。AJAX 最吸引人的就是它的“异步”特性,也就是说它可以在不重新刷新页面的情况下与服务器通信,交换数据,或更新页面。

    AJAX 应用最主要的两个特点

    • 在不重新加载页面的情况下发送请求给服务器。

    • 接受并使用从服务器发来的数据。

    • 不但要向服务器发起请求,还要能够接收服务器响应的结果。这一点初学者一定要意识到。

请求报文&响应报文

http - 请求报文

浏览器 与 服务器进行通讯时,每次浏览器发出的请求,叫请求报文

组成 与 查看方法

请求报文组成:

  1. 请求行

  2. 请求头

  3. 空行

  4. 请求体

具体格式如下图:

小结

  • 请求报文是自动生成,还是人为设置的?

    1. 由浏览器自动生成,也可以人为的修改或添加

  • 请求的方法和地址

    • 在请求报文的哪里?

    • 请求行中

http - 响应报文

组成 与 查看方法

浏览器 与 服务器进行通讯时,每次服务器的响应,叫响应报文

响应报文由:

  1. 状态行

  2. 响应头部

  3. 空行

  4. 响应体

具体格式如下图:

小结

  • 响应报文中状态码在哪里?

    状态行

  • 响应报文中的服务器返回的内容在哪里?

    响应(主)体

http - 响应状态码

服务器响应的内容中除了响应体以外,还有一个需要重点关注的信息,http状态码

服务器对本次请求所处理的结果以一个编码进行体现,这个状态码是我们需要关注的。

传送门:MDN-HTTP状态码 HTTP response status codes

概念 与 作用

  • 概念

状态码反应了,服务器对本次请求所处理的结果,由三位数字组成。

  • 作用

状态码会作为前端人员判断请求处理结果的依据。

常见的状态码

不仅仅只有这几个,这里只是列举了常见的

状态码状态码描述说明
200OK请求成功。
201Created资源在服务器端已成功创建。
304Not Modified资源在客户端被缓存,响应体中不包含任何资源内容!
400Bad Request客户端的请求方式、或请求参数有误导致的请求失败!
401Unauthorized客户端的用户身份认证未通过,导致的此次请求失败!
404Not Found客户端请求的资源地址错误,导致服务器无法找到资源!
500Internal Server Error服务器内部错误,导致的本次请求失败!

小结

状态码很多,但对于前端更多的是关注以下几个状态码:

  • 200

    请求被成功处理

  • 401

    在涉及到身份认证操作时,身份认证失败

  • 404

    url地址错误

  • 400

    请求参数错误

    XMLhttpRequest介绍

    Ajax(Asynchronous JavaScript and XML)不是指一种单一的技术,而是有机地利用了一系列相关的技术。虽然其名称包含XML,但实际上数据格式可以由JSON代替,进一步减少数据量,形成所谓的AJAJ。为了使用JavaScript向服务器发出 HTTP 请求,需要一个提供此功能的类的实例。这就是XMLHttpRequest的由来。这样的类最初是在Internet Explorer中作为一个名为XMLHTTP的ActiveX对象引入的。然后,Mozilla,Safari和其他浏览器,实现一个XMLHttpRequest类,支持Microsoft的原始ActiveX对象的方法和属性。同时微软也实现了XMLHttpRequest

    • header

      将要被赋值的请求头名称

    • value

      给指定的请求头赋的值

  • 显而易见XMLHttpRequest类是重中之重了。

    XMLhttpRequest属性

  • onreadystatechange

    一个JavaScript函数对象,当readyState属性改变时会调用它。回调函数会在user interface线程中调用。

     

    readyState

    HTTP 请求的状态.当一个 XMLHttpRequest 初次创建时,这个属性的值从 0 开始,直到接收到完整的 HTTP 响应,这个值增加到 4。

    5 个状态中每一个都有一个相关联的非正式的名称,下表列出了状态、名称和含义:

    状态名称描述
    0Uninitialized初始化状态。XMLHttpRequest 对象已创建或已被 abort() 方法重置。
    1Openopen() 方法已调用,但是 send() 方法未调用。请求还没有被发送。
    2SentSend() 方法已调用,HTTP 请求已发送到 Web 服务器。未接收到响应。
    3Receiving所有响应头部都已经接收到。响应体开始接收但未完成。
    4LoadedHTTP 响应已经完全接收。

    readyState 的值不会递减,除非当一个请求在处理过程中的时候调用了 abort() 或 open() 方法。每次这个属性的值增加的时候,都会触发 onreadystatechange 事件句柄。

    responseText

     

    目前为止为服务器接收到的响应体(不包括头部),或者如果还没有接收到数据的话,就是空字符串。

    如果 readyState 小于 3,这个属性就是一个空字符串。当 readyState 为 3,这个属性返回目前已经接收的响应部分。如果 readyState 为 4,这个属性保存了完整的响应体。

    如果响应包含了为响应体指定字符编码的头部,就使用该编码。否则,假定使用 Unicode UTF-8。

    responseXML

    对请求的响应,解析为 XML 并作为 Document 对象返回。

    status

     

    由服务器返回的 HTTP 状态代码,如 200 表示成功,而 404 表示 "Not Found" 错误。当 readyState 小于 3 的时候读取这一属性会导致一个异常。

    statusText

    这个属性用名称而不是数字指定了请求的 HTTP 的状态代码。也就是说,当状态为 200 的时候它是 "OK",当状态为 404 的时候它是 "Not Found"。和 status 属性一样,当 readyState 小于 3 的时候读取这一属性会导致一个异常。

    XMLHttpRequest方法

    abort()

    取消当前响应,关闭连接并且结束任何未决的网络活动。

    这个方法把 XMLHttpRequest 对象重置为 readyState 为 0 的状态,并且取消所有未决的网络活动。例如,如果请求用了太长时间,而且响应不再必要的时候,可以调用这个方法。

    getAllResponseHeaders()

     

    把 HTTP 响应头部作为未解析的字符串返回。

    如果 readyState 小于 3,这个方法返回 null。否则,它返回服务器发送的所有 HTTP 响应的头部。头部作为单个的字符串返回,一行一个头部。每行用换行符 "\r\n" 隔开。

    getResponseHeader()

    返回指定的 HTTP 响应头部的值。其参数是要返回的 HTTP 响应头部的名称。可以使用任何大小写来制定这个头部名字,和响应头部的比较是不区分大小写的。

    该方法的返回值是指定的 HTTP 响应头部的值,如果没有接收到这个头部或者 readyState 小于 3 则为空字符串。如果接收到多个有指定名称的头部,这个头部的值被连接起来并返回,使用逗号和空格分隔开各个头部的值。

    open()

    初始化一个请求. 该方法用于JavaScript代码中;如果是本地代码, 使用 openRequest()方法代替.

     

    注意: 在一个已经激活的request下(已经调用open()或者openRequest()方法的request)再次调用这个方法相当于调用了abort()方法。

    参数

    • method

      请求所使用的HTTP方法; 例如 "GET", "POST", "PUT", "DELETE"等. 如果下个参数是非HTTP(S)的URL,则忽略该参数.

    • url

      该请求所要访问的URL

    • async

      一个可选的布尔值参数,默认为true,意味着是否执行异步操作,如果值为false,则send()方法不会返回任何东西,直到接受到了服务器的返回数据。如果为值为true,一个对开发者透明的通知会发送到相关的事件监听者。这个值必须是true,如果multipart 属性是true,否则将会出现一个意外。

    • user

    • 用户名,可选参数,为授权使用;默认参数为空string.
    • password

      密码,可选参数,为授权使用;默认参数为空string.

    send()

    发送 HTTP 请求,使用传递给 open() 方法的参数,以及传递给该方法的可选请求体。

    setRequestHeader()

    向一个打开但未发送的请求设置或添加一个 HTTP 请求(设置请求头)。

    参数

Ajax原生实现

  1. 实例化 XMLHttpRequest 异步对象

    let xhr = new XMLHttpRequest() XMLHttpRequest 是内置的异步对象

  2. 设置 请求方式 与 请求地址

    xhr.open( 请求方式, 请求地址 ) 相当于axios配置对象里的 url与method

  3. 发送请求

  4. xhr.send()
  5. 注册 处理响应的回调函数

    xhr.onload = function(){} 相当于then()里的回调函数

原生git传参

  • get方法如何传递参数

  • 直接在url后拼接即可, url?key=value&key=value

<script>
        document.querySelector('button').onclick = function () {
            // 1. 实例化 `XMLHttpRequest` 异步对象
            let xhr = new XMLHttpRequest()
​
            // 2. 设置 请求方式 与 请求地址
            xhr.open('get', 'https://autumnfish.cn/api/joke?name=zs&age=20')
​
            // 3. 发送请求
            xhr.send()
​
            // 4. 注册 处理响应的回调函数
            xhr.onload = function () {
                console.log(xhr.response);
            }
        }
    </script>

原生post传数据

语法

  • xhr.send(数据)

    • 用于向服务器发送数据

  • xhr.setRequestHeader('content-type', '数据的格式')

    只要是数据就要明确的告诉服务器所发送的数据的格式是什么! axios会自动根据数据格式,自动设置content-type, 由于是原生语法,所以要手动设置content-type 👉 setRequestHeader()要在send()之前设置,因为只要执行了send()本次请求就完成了,后面的代码与本次的请求没有任何关系了。

<script>
     document.querySelector('button').onclick = function(){
     let xhr = new XMLHttpRequest()
            
     xhr.open('post', 'https://autumnfish.cn/api/form/urlencoded')
            
     xhr.onload = function(){
     console.log(xhr.response);
     }
    
     // 设置content-type
     xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded')
     // urlencoded格式
     let data = 'username=thinker&age=20'
     xhr.send(data)
      }
 </script>

传递 json 格式

<script>
    document.querySelector('button').onclick = function(){
    let xhr = new XMLHttpRequest()
            
    xhr.open('post', 'https://autumnfish.cn/api/form/json')
            
    xhr.onload = function(){
  // xhr.response接收响应的数据
           console.log(xhr.response);
​
  // JSON.parse解析接收到的响应数据
            console.log(JSON.parse(xhr.response));
        }
            
 // 设置content-type
    xhr.setRequestHeader('content-type', 'application/json')
 // JSON
   let data = {username:'thinker',age:20};
   let strJSON = JSON.stringify(data)
            
   xhr.send(strJSON)
     }

接收 与 解析

  • 接收

    • 原生ajax请求后,服务器响应的数据要通过 xhr.response来接收

  • 解析

    服务器返回的数据也有多种格式之分,现在使用最广泛的就是json,早期还用过xml。 只要是接口返回的数据几乎都是JSON格式。 解析响应数据也就是解析JSON格式的数据,使用JSON.parse(),如果是向服务器传递则使用JSON.stringify()

Axios

  • AJAX是一种技术,在不重新加载页面的情况下发送请求给服务器。但是原生的代码晦涩难懂,对于初学者很不友好。

  • 有一些人对原生代码进行了封装,简化了原生代码的操作。初学者就可以使用简化后的代码完成ajax操作。

  • axios是目前最为流行的代码库,在浏览器端是基于Ajax封装

axios - get请求语法

语法一

// 无参数
axios.get(url)
​
// 有参数,参数拼接在URL中
axios.get(url?key=value&key=value)
--------------------------------------
// 1. 无参请求
      axios.get('https://autumnfish.cn/api/joke').then(function(response){
        console.log(response);
      })
   
// 2. 有参数,通过url传递
      axios.get('https://autumnfish.cn/api/joke/list?num=3').then(function(response){
     console.log(response);

语法二(推荐)

使用 params 发起带参请求

推荐方式
axios({
    method:'GET',
    url:'https://autumnfish.cn/api/joke',
    params:{num:3}
    }).then(function(res){console.log(res)})

axios - post请求

语法一

axios.post(url,{key:value,key:value}).then(function(response){
console.log(response)
})

语法二(推荐)

data 是作为请求体被发送的数据

  • 仅适用 PUT , POST , DELETEATCH 请求方法

推荐方式
axios({
    method:'POST',
    url:'https://autumnfish.cn/api/user/check',
    data:{name:zs,age:18}
    }).then(function(res){console.log(res)})

设置content-type 语法

  • 如果是人为的使用axios向服务器发送数据,就需要设置相应的 content-type

  • 前端,只会向服务器发送数据,所以只需要设置请求报文的 content-type即可

具体参考MDN

axios 设置 content-type

axios 在配置对象中通过 headers 来设置content-type,格式:

axios.post(url,data,{headers:{'content-type':'内容格式类型'}})
​
axios({
    url:'',
    method:'post',
    data:{},
    headers:{
        'content-type':'内容格式类型'
    }
})

接口中的 content-type

不同接口对于提交数据格式的要求略有不同,咱们结合3个测试用接口,来看看如何通过axios如何提交不同格式的数据,之后看到类似的需求能够选择对应的格式进行提交

测试接口

1.FormData数据提交 接口

 axios({
          url:'https://autumnfish.cn/api/form/formdata',
          method:'post',
          data:fd,
          headers:{'content-type':'multipart/form-data'}
  })

2.application/json数据提交 接口

 axios({
          url:'https://autumnfish.cn/api/form/json',
          method:'post',
          data:{
            name:'thinker',
            age:20,
          },
          headers:{'content-type':'application/json'}
 })

3.application/x-www-form-urlencoded数据提交 接口

axios({
      url:'https://autumnfish.cn/api/form/urlencoded',
     method:'post',
     data:{
        name:'thinker',
        age:20,
        },
     headers:{'content-type':'application/x-www-form-urlencoded'}
   })

axios默认配置

全局默认请求基地址
axios.defaults.baseURL = 'https://api.example.com';
​
全局默认token
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
​
全局默认请求头配置
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

拦截器

在请求或响应被 then 或 catch 处理前拦截它们。

// 添加请求拦截器
axios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });
​
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    return response;
  }, function (error) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    return Promise.reject(error);
  });

演示

axios.interceptors.request.use(function(config){
            console.log('请求拦截器执行了')
            return config
        },function(err){})
​
        axios.interceptors.response.use(function(res){
            console.log('响应拦截器执行了')
            const a = 'jh'  //设置响应的数据
            return a
        },function(err){})
        axios({
            method:'POST',
            url:'http://ajax-api.itheima.net/register',
            data:{
                username:'ahsdfsdddjkmzzh',
                password:'123456'
            },
            a:(function() {
                console.log('请求')})()
            }
        ).then(function(res){
            console.log('响应')
            console.log(res)
        })
-------------------------------------------
//执行顺序
                    请求
设置拦截器.html:14   请求拦截器执行了
设置拦截器.html:19   响应拦截器执行了
设置拦截器.html:34   响应
设置拦截器.html:35   jh

如果你稍后需要移除拦截器,可以这样:

const myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);

可以给自定义的 axios 实例添加拦截器。

const instance = axios.create();
instance.interceptors.request.use(function () {/*...*/});

错误处理

请滚去看文档

axios.get('/user/12345')
  .catch(function (error) {
    if (error.response) {
      // 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围
      console.log(error.response.data);
      console.log(error.response.status);
      console.log(error.response.headers);
    } else if (error.request) {
      // 请求已经成功发起,但没有收到响应
      // `error.request` 在浏览器中是 XMLHttpRequest 的实例,
      // 而在node.js中是 http.ClientRequest 的实例
      console.log(error.request);
    } else {
      // 发送请求时出了点问题
      console.log('Error', error.message);
    }
    console.log(error.config);
  });

form表单提交数据

  • 真实项目中的数据都是由浏览者输入的,而form表单就是被设计用来收集浏览者输入的数据,form表单不但具有采集数据的作用,还有提交数据的能力(不需要JavaScript)。

  • 但原生的提交方式会造成页面跳转,所以这种方式现在几乎不再使用,了解即可。

form原生提交

  1. form - 原生提交数据的基本配置

    • form标签的action属性用于设置服务器接

      用于设置服务器接口 form标签内所有的表单元素都会提交到action指向的url

    • form标签的method属性

      用于设置请求的方式 get或post

    • 所有的表单元素必须设置name属性

      name属性用于设置服务器所接收的数据项的key

    • 提交按钮 submit

示例

<body>
     action用于设置表单提交时所请求的接口
     method用于设置请求方式
<form action="请求url 地址" method="请求方式">
       name 属性值 将作为 key
       表单输入的内容 将作为 value
   <input class="username" type="text" placeholder="用户名" name="username">
   <input class="password" type="password" placeholder="密码" name="password">
   <input class="submit" type="submit" value="提交">
</form>
</body>

ajax提交数据

虽然form表单的原生提交数据已成为过去,但我们仍然需要使用form表单来收集数据,而使用ajax提交,来替换原生的提交

步骤

  1. 阻止默认行为

    由于ajax提交,与form表单提交都需要通过点击 提交按钮。而form表单的原生提交属于表单的默认行为,所以需要阻止这个默认行为,而执行js的ajax提交行为

  2. 收集表单数据

    获取表单数据,组织成 axios 的data 参数

  3. 发起ajax请求

    按之前的方式发起ajax请求, 选择方法,设置url,稍许的不同在于数据是从表单中获取到的。

示例

 <script>
        let oBtn = document.querySelector('.submit')
       // 为按钮 .submit 按钮注册事件
        let oBtn = document.querySelector('.submit')
        oBtn.onclick = function(e){
            // 阻止表单的默认行为
            e.preventDefault()
            
            // 收集表单数据
            let data = {
                username:document.querySelector('.username').value,
                password:document.querySelector('.password').value
            }
​
            // console.log(data);
            // 发起 ajax 请求
            axios.get("https://autumnfish.cn/api/form/submit",{params:data}).then(function(res){
                console.log(res);
            })
​
    </script>

但是:这种方法需要自己 自己构造key:value数据很繁琐

form-serialize插件

插件下载地址

如果form表单内有很多的表单项,取值的代码也会有很多,这一节咱们来学习form-serialize插件来简化取值

使用步骤

  • 1.引入form-serialize

    引入后会在全局注册一个serialize()方法

  • 2.调用serialize即可得到表单内所有的数据

    serialize(form标签对象, {hash:true})

    表单数据被组织成对象 {key:value, key:value}

    serialize(form标签对象, {hash:false})

    表单数据被组织成 key=value&key=value 格式的键值对

    注意:表单元素必须有name属性,

示例

 <!-- 1. 导入 form-serialize插件 -->
 <script src="./02-其他资料/lib/form-serialize.js"></script>
    <script>
        let oBtn = document.querySelector('.submit')
        oBtn.onclick = function(e){
            e.preventDefault()
​
            // 2. 调用 form-serialize方法
            let oForm = document.querySelector('form')
            let data = serialize(oForm, { hash: true })
            // console.log(data);
​
        axios.get("https://autumnfish.cn/api/form/submit",                          {params:data}).then(function(res){
                console.log(res);
            })
            
        }
 </script>

FormData 基本用法

上一节咱们是通过插件来获取表单数据,JavaScript提供了一个内置对象FormData,也可以实现类似效果,而且不仅仅是文本类数据,文件也可以,咱们先尝试文本类的数据

简介

  • FormData是浏览器内置的对象,其作用等价于 form-serialize。

  • FormData对象内部使用key value形式存储表单数据项

  • 能够结合ajax进行操作

基本语法

  • 1.实例化FormData对象

    • new FormData(form标签对象)

    由form标签创建FormData对象,在有form标签的情况下

    • new FormData()

    创建一个空的FormData对象,在没有form标签的情况下使用

  • 2.实例上的方法

    • .get(key)

    返回在 FormData 对象中与给定键关联的第一个值

    • .append(key, value)

    FormData 中添加新的属性值,FormData 对应的属性值存在也不会覆盖原值,而是新增一个值,如果属性不存在则新增一项属性值

    • .set(key,value)

    FormData 设置属性值,如果FormData 对应的属性值存在则覆盖原值,否则新增一项属性值

示例

<script>
        let oBtn = document.querySelector('.submit')
        oBtn.onclick = function(e){
            e.preventDefault()
​
            // 1. 基于表单 实例化 FormData 对象
            let oForm = document.querySelector('form')
            
            let fd = new FormData(oForm)
            // console.log(fd);
            // 2. 直接将 FormData 实例作为数据传递
         axios.get("https://autumnfish.cn/api/form/submit",                          {params:fd}).then(function(res){
                console.log(res);
        })    
    }
</script>

文件上传表单 - 补充

accept属性

accept属性用于过滤出指定类型的文件供选择 MDN传送门

    <input accept="image/png, image/jpg" type="file" name="avatar" placeholder="请选择头像">
    <input accept=".png, .jpg" type="file" name="avatar" placeholder="请选择头像">

onchange事件

选择的文件变更时触发此事件

<script>
    // 选择的文件变更时触发此事件
    document.querySelector('input').onchange = function(){
        // console.log('hello');            
    }
</script>

获取文件对象

<script>
    // 选择的文件变更时触发此事件
    document.querySelector('input').onchange = function(e){
        // console.log('hello');
        // console.log(this.value);         // 获取的是选择的路径名,这一个字符串
        console.log(e.target.files[0]);     // 获取选择的文件对象
    }
​
</script>

小结

  • accept属性是否可限制用户选择文件

    不可以,仅是简单的过滤

  • onchange事件什么时候触发

    当选择的文件有所变化时

  • 如何获取 文件对象

    e.target.files[0]

    promise

promise-介绍

Promise,译为承诺,是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和更加强大

  • 在以往我们如果处理多层异步操作,我们往往会像下面那样编写我们的代码

setTimeout(function(){
            console.log('第1步');
            setTimeout(function(){
                console.log('第2步');
                setTimeout(function(){
                    console.log('第3步');
                    setTimeout(function(){
                        console.log('第4步');
                    },1000)
                },3000)
            },2000)
        },4000)
  • 阅读上面代码,是不是很难受,上述形成了经典的回调地狱

  • 现在通过Promise的改写上面的代码

new Promise(function (resolve, reject) {
            setTimeout(function () {
                resolve('第 1 步')
            }, 3000)
        }).then(function (res) {      // 属于第1个new Promise实例
            console.log(res);  //返回上一个执行成功的数据
            return new Promise(function (resolve, reject) {
                setTimeout(function () {
                    resolve('第 2 步')
                }, 2000)
            })
        }).then(function (res) {
            console.log(res);
            return new Promise(function (resolve, reject) {
                setTimeout(function () {
                    resolve('第 3 步')
                }, 5000)
            })
        }).then(function (res) {
            console.log(res)
        })

瞬间感受到promise解决异步操作的优点:

  • 链式操作减低了编码难度

  • 代码可读性明显增强

下面我们正式来认识 promise

Promise - 基本语法

概念

Promise是一个对象,内部执行指定异步任务,并在成功后失败时执行预定的处理代码。

基本语法

Promise相关的有三要素:所有执行异步任务,成功后的代码,失败后的代码,这三要素都要体现在语法里。

语法格式

  1. 1.Promise是一个对象

    new Promise()

  2. 2.Promise实例化时必须传递一个回调函数

    new Promise( function(){} )

  3. 3.回调函数必须有两从此参数

    new Promise( function(resolve, reject){ })

回调函数的使用原则

  1. 1.回调函数内部主要用于执行异步操作(时间不确定的操作)

    new Promise( function( resolve, reject ){ // 这里执行异步操作 })

  2. 2.根据异步操作结果的选择性的调用resolve 或 reject

    new Promise( function(resolve, reject){

    let 结果 = 这里执行异步操作

    if(结果成功){

    resolve(结果)

    }else{

    reject(失败) }

    })

这里一定要注意,异步操作虽然在回调函数内部执行,但回调函数的结果并不在回调函数内处理。

分情况处理结果

new Promise( function(resolve, reject){

let 结果 = 这里执行异步操作

if(成功){

resolve(结果)

}else{

reject(失败)

}

}).then( function(data){

// resolve会跳到then里执行,data就是resolve()传递过来的数据

}).catch( function(err){

// reject会跳到catch里执行,err就是reject()传递过来的错误

})

说明:

  • new Promise()

    创建对象,执行异步任务,根据结果选择性执行resolve(数据) 或 reject(错误) resolve()与reject()并不是处理结果,只是通过这种语法将成功情况的代码,与失败情况的代码放到Promise回调函数的外面进行处理,由此减少了一层函数的嵌套。

  • then() 成功时的处理

    Promise回调函数,执行异步任务时,如果异步结果是失败的,将会使用reject,将错误 导向catch代码块 进行处理。

  • catch() 失败时的处理

    Promise的catch方法,用于处理reject传递过来的错误,

  • finally() 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作

  • promise .then(result => {···}) .catch(error => {···}) .finally(() => {···});

可以这样理解,通过 Prmoise 来执行异步函数,可以在回调函数外部处理异步操作的结果

示例代码

<script>
    let pro = new Promise(function(resolve,reject){
        // setTimeout代表一个异步操作
        setTimeout(function(){
            // 模拟异步操作产生的数据
            let v = Math.floor(Math.random()*10 + 1);
            // 将成功或失败的发送到其他代码块
            if(v % 2 ==0){
                resolve('成功的数据')
            }else{
                reject('失败的原因')
            }
        })
    })
​
    pro.then(function(data){
        // 成功时处理的代码块
        console.log('成功 then被执行');
    }).catch(function(err){
        // 失败时处理的代码块
        console.log('失败 catch被执行');
    })
</script>

小结

  • 1.如何理解Promise?

    Promise本质就是一个对象,用于执行指定的异步任务的工具代码。

  • 2.Promise语法两部分?

    创建对象并指派任务 分情况处理结果

  • 3.创建Promise对象时相关的参数要求?

    提供回调函数,回调函数里定义两个参数resolve与reject

  • 4.resolve与reject分别什么时候调用?

    resolve成功时调用,传递数据 reject失败时调用,传递错误信息

  • 5.resolve、reject与then、catch的关系?

    resolve对应then reject对应catch

Promise - 链式调用

  • 语法

    new Promise().then().then().then()...

<script>
        new Promise(function(resolve,reject){
            resolve('hello 1')
        }).then(function(res){      // 属于第1个new Promise实例
            console.log(res);
            return new Promise(function(resolve,reject){
                resolve('hello 2')
            })
        }).then(function(res){
            console.log(res);
            return new Promise(function(resolve,reject){
                resolve('hello 3')
         })
        }).then(function(res){
            console.log(res);
        })
    </script>

小结

  • then()属于哪个Promise对象

    • 前面的最近的then里的return 的Promise实例对象

Promise的三种状态

在Promise执行的过程中,内部会经历三种状态:penddingfulfilledrejected .传送门:MDN-Promise

  • 小结:三种状态

    1. pendding :初始状态,等待结果 对应的代码为new Promise()

    1. fulfilled :收到结果,结果是成功 对应的代码为resolve()

    1. rejected :收到结果,结果是失败 对应的代码为reject()

实际开发中,我们更多使用的是别人提取好的,例如: axios

promise- 构造函数方法

Promise构造函数存在以下方法:

  • all()

  • race()

  • allSettled()

  • resolve()

  • reject()

  • try()

Promise.all

Promise除了按顺序一次一个一次一个的执行异步任务外,还可以一次执行多个异步任务。 传送门:Promise.all

  • all是静态方法 静态方法是通过函数 点 出来的方法

  • 语法

Promise.all( [ promise1, promise2 , ... ] ).then(function(data){
​
}).catch(function(err){
​
})

说明

  • 1.用数组存储多个promise实例,每个实例指派了一个异步任务,再把这个数组传递给all方法,这样就开始带着多个任务执行

  • 2.Promise.all 等待 所有都成功 或 任何一个失败

    • 全部成功:then接收到的数据是所有的promise实例resolve的数据,是一个数组

    • 只要有1个失败:catch接收到的是第1个失败的reject的错误.

    注意:最算有失败的出现,每一个promise实例也要执行

示例代码

<script>
    let proa = new Promise(function (resolve, reject) {
        resolve('proa resolve的结果');
        // reject('proba reject的错误')
    })
    let prob = new Promise(function (resolve, reject) {
        resolve('prob resolve的结果');
        // reject('prob reject的错误')
    })
    let proc = new Promise(function (resolve, reject) {
        resolve('proc resolve的结果');
        // reject('proc reject的错误')
    })
​
    // 传递多个 promise实例给 Promise.all()
    Promise.all([proa, prob, proc]).then(function (data) {
        console.log(data);
    }).catch(function (err) {
        console.log(err);
    })
</script>

Promise.race

用于同时指派多个异步任务时使用,取第1个有结果的异步任务的结果作为Promise.race的结果。传送门:Promise.race

语法

Promise.race([ promise1, promise2, ... ]).then(function(data){
}).catch(function(err){
})

说明

  • 用数组存储多个promise实例,每个实例指派了一个异步任务,再把这个数组传递给race方法,这样就开始带着多个任务执行

  • Promise.race第1个有结果的异步的结果作为Promise.race的结果

    也就是第1个执行了resolve或reject的promise。注意不是传递的第1个,由于Promise里的异步任务完成的时间是不同的,哪个Promise有结果,就取哪个结果作为Promise.race的结果

示例代码

let proa = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve('proa resolve的结果');
        // reject('proa reject的结果');
    }, 2000)
})
let prob = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve('prob resolve的结果');
        // reject('prob reject的结果');
    }, 1000)
})
let proc = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve('proc resolve的结果');
        // reject('proc reject的结果');
    }, 3000)
})
​
// 传递多个 promise实例给 Promise.race()
Promise.race([proa, prob, proc]).then(function (data) {
    console.log(data);
}).catch(function (err) {
    console.log(err);
})
  • Promise.all 与 Promise.race的区别,及应用场景

    • Promise.all

      所有的都成功,或要有1个失败 应用场景:页面的渲染需要多个接口的数据

    • Promise.race

      等待第1个有结果的异步任务 应用场景:一个数据有多个接口可以获取,等待最快的。

allSettled()

Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例

只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];
​
Promise.allSettled(promises).
  then((results) => results.forEach((result) => console.log(result.status)));
// 结果
// "fulfilled"
// "rejected"

resolve()

将现有对象转为 Promise对象

Promise.resolve('foo')
// 等价于
new Promise(function(resolve){
    resolve('foo')
})

参数可以分成四种情况,分别如下:

  • 参数是一个 Promise 实例,promise.resolve将不做任何修改、原封不动地返回这个实例

  • 参数是一个thenable对象,promise.resolve会将这个对象转为 Promise对象,然后就立即执行thenable对象的then()方法

  • 参数不是具有then()方法的对象,或根本就不是对象,Promise.resolve()会返回一个新的 Promise 对象,状态为resolved

  • 没有参数时,直接返回一个resolved状态的 Promise 对象

reject()

Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为 rejected

const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
​
p.then(null, function (err) {  // null代表没有执行成功
  console.log(err)
});
// 出错了

Promise.reject()方法的参数,会原封不动地变成后续方法的参数

Promise.reject('出错了')
.catch(e => {
  console.log(e === '出错了')
})
// true

使用场景

将图片的加载写成一个Promise,一旦加载完成,Promise的状态就发生变化

const preloadImage = function (path) {
  return new Promise(function (resolve, reject) {
    const image = new Image();
    image.onload  = resolve;
    image.onerror = reject;
    image.src = path;
  });
};

通过链式操作,将多个渲染数据分别给个then,让其各司其职。或当下个异步请求依赖上个请求结果的时候,我们也能够通过链式操作友好解决问题

// 各司其职
getInfo().then(res=>{
    let { bannerList } = res
    //渲染轮播图
    console.log(bannerList)
    return res
}).then(res=>{
    
    let { storeList } = res
    //渲染店铺列表
    console.log(storeList)
    return res
}).then(res=>{
    let { categoryList } = res
    console.log(categoryList)
    //渲染分类列表
    return res
})

通过all()实现多个请求合并在一起,汇总所有请求结果,只需设置一个loading即可

function initLoad(){
    // loading.show() //加载loading
    Promise.all([getBannerList(),getStoreList(),getCategoryList()]).then(res=>{
        console.log(res)
        loading.hide() //关闭loading
    }).catch(err=>{
        console.log(err)
        loading.hide()//关闭loading
    })
}
//数据初始化    
initLoad()

通过race可以设置图片请求超时

//请求某个图片资源
function requestImg(){
    var p = new Promise(function(resolve, reject){
        var img = new Image();
        img.onload = function(){
           resolve(img);
        }
        //img.src = "https://b-gold-cdn.xitu.io/v3/static/img/logo.a7995ad.svg"; 正确的
        img.src = "https://b-gold-cdn.xitu.io/v3/static/img/logo.a7995ad.svg1";
    });
    return p;
}
​
//延时函数,用于给请求计时
function timeout(){
    var p = new Promise(function(resolve, reject){
        setTimeout(function(){
            reject('图片请求超时');
        }, 5000);
    });
    return p;
}
​
Promise
.race([requestImg(), timeout()])
.then(function(results){
    console.log(results);
})
.catch(function(reason){
    console.log(reason);
});

如何保证一个promise失败之后,promise.all还能正常收到结果

const promise1 = Promise.resolve(3);
const promise2 = Promise.reject(new Error());
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});
​
Promise.all([promise1, promise2, promise3].map(p=>p.catch(e=>e))).then((values) => {
  console.log(values);
});

async函数 与 await

  • Promise已经可以很好的解决回调嵌套问题啦,咋又来一个?他的作用是简化Promise调用时的写法,把最后一层then也给拿掉,使用 = 来接收异步的结果

  • 简单粗暴的理解方式就是:不再使用 .then(function(数据){}),而直接使用 = 来接收数据 传送门:async函数

async 函数基本使用

语法

async function 函数名(){
     
}
  • 说明

    • 使用async修饰的函数就是async函数

    • async函数与普通函数区在于返回值

    • async函数会隐匿返回一个Promise对象,所以async函数调用后可以直接进行 .then() 操作 而函数内部的return则会将数据 传递给 then的回调函数

  • 原理

  • 可以理解async函数内有两个return,隐式的return,显示的return

  • 隐式的return返回一个Promise对象,用于调用后面的thne 而显示的return返回数据给then内的回调函数

代码示例

<script>
    /* 
        // 定义一个async函数
        async function fn() {
​
        }
​
        // async 会隐式返回一个Promise对象
        console.log(fn());
​
        // 所以可以调用后面的 then(function(res){})
        fn().then(function (res) {
            console.log('then被调用了');
        })
    */
​
    async function fn() {
    // 显示return一个数据
    return 100
    }
​
    fn().then(function(res){
        // 显示return的数据会传递给 then的回调函数的参数res
        console.log(res);
    })
</script>

async函数与await

  • async函数 与 await,配合使用可以简化Promise的resolve数据的接收

语法格式

<script>
  async function fn(){
      let 变量 = await Promise对象
  }    
</script>

语法格式

<script>
​
    // async函数 与 await,配合使用可以简化Promise的resolve数据的接收
    // Promise resolve的数据 默认接收方式
    new Promise(function(resolve,reject){
        resolve('hello')
    }).then(function(res){          // promise内的resolve数据需要使用 .then进行接收
        console.log(res);
    })
​
    // async 与 await简化接收方式
    async function fn1(){
        let res = await new Promise(function(resolve,reject){
            resolve('hello')
        })
        console.log(res);
    }
​
    fn1();
</script>

async 异常捕获

  • Promise有两种结果:resolve结果,与reject结果

  • 上一小节中讲解了使用async函数简化了Promise实例的resolve结果,那么reject的结果如何处理呢?

  • 解决办法使用,使用try ... catch 代替 .catch()

    // 异常处理语法回顾
    try{
​
    }catch(e){
        
    }
    try ... catch 可以将try块内发现的异常捕获到catch块内进行处理

代码示例

<script>
​
    // async可以使用 await 来处理Promise内部resolve的数据
    // 但 Promise的reject的情况如何来处理呢?
    
    // async 与 await简化接收方式
    async function fn1(){
        try {
            let res = await new Promise(function(resolve,reject){
                if(Math.floor(Math.random()*10) % 2 == 0){
                    resolve('成功')
                }else{
                    reject('失败')
                }
            })
            console.log(res);
        } catch (error) {
            console.log(error);
        }
    }
​
    fn1();
</script>
  • 小结重点

  • async函数里的错误(promise实例reject的错误)可以使用什么捕获?

    try ... catch 是通用的异常处理语法,所有的异常都可以捕获 而.catch只能捕获 Promise的异常

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值