最易理解的Promise对象极简教程

882 阅读2分钟

最易理解的Promise对象教程

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情

Promise是异步编程的一种解决方案,它最早由社区提出并实现,ES6将Promise写进了语言标准,并原生提供Promoise对象。

一项技术的产生,必定是解决特定问题。那么,Promise解决了什么问题呢?

一 为什么需要Promise?

1.1 什么是回调地狱

常见的一个需求:通过第一次Ajax请求获取到id,然后再发起第二次Ajax请求,根据id获取用户名username,最后发起第三次Ajax请求,根据用户名username获取所在单位company。

我们模拟一下响应数据,通过jquery的ajax方法来实现。

data.json

 {
     "id":1
 }

data2.json

 {
     "username":"沈麽鬼"
 }

data3.json

 {
     "company":"红鲤鱼与绿鲤鱼与驴公司"
 }

示例 思考:ajax在哪里发送第二个请求?

 // 发送ajax请求
 $.ajax({
     type:'GET',
     url:'./data.json',
     success: (res) => {  //response,result
         console.log(res);
         const id = res.id //拿到响应的结果
         console.log(id); // =>id = 1
     }
 })
 // 思考:ajax在哪里发送第二个请求??
 $.ajax({
     type:'GET',
     url:'./data2.json',
     data:{
         id
     },// 这里能获取到id吗?id is not defined 不能!!id的作用域不同
     success: (res) => {
         console.log(res);
     }
 })

在上面的例子中,第二次的请求参数id依赖于前一次的响应结果,由于id的作用域不同,第二次ajax请求拿不到id,所以不能成功!

那么第二次ajax请求,要嵌套第一个ajax里面的success回调函数中。

示例

 // 发送ajax请求
 $.ajax({
     type:'GET',
     url:'./data.json',
     success: (res) => {  //response,result
         const id = res.id //拿到响应的结果
         // 第二次ajax请求
         $.ajax({
             type:'GET',
             url:'./data2.json',
             data:{
                 id
             },
             success: (res) => {
                 console.log(res); // =>{username: "拓宇"}
             }
         })
     }
 })

同样,第三次ajax请求,要嵌套第二个ajax里面的success回调函数中。

示例

 // 发送ajax请求
 $.ajax({
     type:'GET',
     url:'./data.json',
     success: (res) => {  //response,result
         const id = res.id //拿到响应的结果
         // 第二次ajax请求
         $.ajax({
             type:'GET',
             url:'./data2.json',
             data:{
                 id
             },
             success: (res) => {
                 const userName = res.username
                 // 第三次ajax请求
                 $.ajax({
                     type:'GET',
                     url:'./data3.json',
                     data:{
                         userName
                     },
                     success: (res) => {
                         console.log(res); // =>{company: "红鲤鱼与绿鲤鱼与驴公司"}
                     }
                 })
             }
         })
 ​
     }
 })

上面的案例中,在回调函数中嵌套回调函数,这种形式,我们就叫做回调地狱

回调地狱的问题一是代码结构很乱,调试不方便,二是业务需求变化时,不便维护。

1.2 Promise的最佳实践

为了解决这个问题,社区提出并实现Promise对象,它是用来解决回调地狱,以线性then()方法调用链的形式表达一连串异步操作,而无须把每个操作嵌套在前一个操作的回调内部,使我们代码看起来更加易读、优雅、扁平化。

二 Promise的基本使用

Promise是一个首字母大写的函数,一般来说,首字母大写的函数,是构造函数。构造函数的作用是通过new关键字实例化对象。

示例 基础语法

 new Promise((resolve,reject)=>{})
  • 23 Promise接受一个函数作为参数

  • 在这个函数中,接受两个参数:

    • resolve 成功函数
    • reject 失败函数

Promise实例有两个属性,PromiseStatus和PromiseValue属性

  • PromiseStatus:状态
  • PromiseValue:结果
 // 调用resolve()
 const p = new Promise((resolve, reject) => {
     
 })
 console.dir(p); // PromiseStatus和PromiseValue属性

2.1 Promise的状态

第一种状态:pending (准备,待解决,进行中)

第二种状态:resolved(已完成,成功)

第三种状态:rejected(已拒绝,失败)

2.2 Promise状态的改变

通过调用resolve()和reject()改变当前promise对象 的状态。

示例

 // 调用resolve()
 const p = new Promise((resolve, reject) => {
     // resolve() 调用函数,可以使当前Promise对象状态改成 resolved
     resolve() // => PromiseStatus:resolved
 })
 console.dir(p);
 // 调用reject()
 const p = new Promise((resolve, reject) => {
     // resolve() 调用函数,可以使当前Promise对象状态改成 resolved
     // resolve() // => PromiseStatus:resolved
     // reject() 调用函数,可以使当前Promise对象状态改成 rejected
     reject()
 })
 console.dir(p); // => PromiseStatus:rejected
  • 调用 resolve() 函数,可以使当前Promise对象状态改成 resolved
  • 调用reject()函数,可以使当前Promise对象状态改成 rejected

Promise的状态是一次性的,即如果调用一次resolve(),再调用reject(),那么reject()不生效。

示例

 const p = new Promise((resolve, reject) => {    
     resolve()
     reject() // 不生效
 })
 console.dir(p); // => PromiseStatus:resolved 而不是 rejected

2.3 Promise的结果

示例

 // 通过调用resolve函数,传递参数,改变当前promise对象的结果
 const p = new Promise((resolve, reject) => {    
     resolve('成功')
 })
 console.dir(p);  // => PromiseValue:"成功"
 // 通过调用reject函数,传递参数,改变当前promise对象的结果
 const p = new Promise((resolve, reject) => {    
     reject('失败')
 })
 console.dir(p);  // => PromiseValue:"失败"

promise的结果,是使用resolve或reject的传递参数,来改变PromiseValue。

三 Promise的方法

3.1 then()方法的使用

then()方法接受两个参数,两个参数都是函数。

第一个函数当promise状态为resolved时,resolve('成功')执行;

第二个函数当promise状态为rejectded时,reject('失败')执行;

示例

 const p = new Promise((resolve, reject) => {    
     resolve('成功')
     // reject('失败')
 })
 // then方法,方法即是就是函数。
 // 函数参数
 // 第一个参数是一个函数  resolve() 执行时调用  
 // 第二个还是一个函数 reject() 执行时调用
 // 返回值
 p.then(() => {
     // 当promise的状态是resolved时 调用
     console.log("成功时调用");
 },() => {
     // 当promise的状态是rejected时 调用
     console.log("失败时调用");
 })
 console.dir(p); 

3.2 then()方法获取数据

通过then方法第一个函数参数中的形参value,拿到resolve('成功')传递的参数;

通过then方法第二个函数参数中的形参error,拿到reject('失败')传递的参数;

示例

 const p = new Promise((resolve, reject) => {    
     resolve('成功')
     // reject('失败')
 })
 // then方法,方法即是就是函数。
 // 函数参数
 // 第一个参数是一个函数  resolve() 执行时调用  
 // 第二个还是一个函数 reject() 执行时调用
 // 返回值
 p.then((value) => {
     // value拿到的是 resolve()传递的参数
     console.log(value); // => "成功" 
 },(error) => {
     // error拿到的是 reject()传递的参数
     console.log(error);
 })

3.3 then()方法的返回值

我们通过打印p.then(),可以看到,then()方法的返回值是一个新的promise对象。

示例

 const p = new Promise((resolve, reject) => {})
 // 返回值 是一个新的promise示例,状态是pending
 p.then(() => {}, () => {})
 console.dir(p.then()); // 返回的是一个新的promise对象

3.4 then ()链式操作

那么,then()既然是一个promse对象,那它也应该有then(),于是我们可以在promise实例中,使用then方法的链式操作。

示例

 // 链式操作
 const p = new Promise((res,rej) => {
 }).then().then().then()

3.5 catch()方法

catch()捕获错误,它在两种情况下被执行:

  • 当promise的状态改成rejected时,执行
  • 当promise代码块中,出现代码错误时,执行

示例

 const p = new Promise((resolve, reject) => {
     // reject()
     console.log(a);
 })
 // 思考: catch() 什么时候执行?
 // 1 当promise的状态改成rejected时,执行
 // 2 当promise代码块中,出现代码错误时,执行
 p.catch((error) => {
     console.log(error); // ReferenceError:  a is not defined
 })

可以发现,catch()和then ()第二个参数函数所触发时机一样。

我们在实际开发中,常常会使用catch()来捕获错误,很少用到then()方法的第二个函数,那么 Promise最常用的用法是下面这个结构:

 // Promise 最佳实践代码
 new Promise((res, rej) => {
 ​
 }).then((value) => {
     console.log(value);
 }).catch((error) => {
     console.log(error);
 })

四 解决回调地狱

4.1 promise 实现

我们回到ajax回调地狱的需求,通过三次嵌套回调,依次从data.json、data2.json、data3.json拿到最终数据。

示例

 // 解决回调地狱
 new Promise((resolve, reject) => {
     $.ajax({
         type:'GET',
         url:'./data.json',
         success: (res) => {
             // 调用res 修改promise状态为成功
             resolve(res);
         },
         error:(error) => {
             reject(error)
         }
     })
 }).then((data) => {
     const id = data.id
     return new Promise((resolve, reject) => {
         $.ajax({
             type:'GET',
             url:'./data2.json',
             data:id,
             success: (res) => {
                 // 调用res 修改promise状态为成功
                 resolve(res);
             },
             error:(error) => {
                 reject(error)
             }
         })
     })
 }).then((data) => {
     const userName = data.username
     return new Promise((resolve, reject) => {
         $.ajax({
             type:'GET',
             url:'./data3.json',
             data:userName,
             success: (res) => {
                 // 获取data3中的数据
                 console.log(res);
             },
             error:(error) => {
                 console.log(error);
             }
         })
     })
 })

上述代码,使得异步回调函数不再嵌套,可读性、可维护性要比回调嵌套的方式优雅很多。

4.2 优化代码-封装Ajax

在4.1的例子中会发现,ajax的代码几乎是通用的,只不过url和参数不同,重复的代码产生了一些冗余,接下来我们把ajax的请求封装成一个函数。

通过then()链式操作,使得异步方法写起来向同步方法一样,代码看起来更加整洁、清晰。

示例

 /**
  * 封装Ajax请求
  */
 function getData(url, data = {}) {
     return new Promise((resolve, reject) => {
         $.ajax({
             type: 'GET',
             url: url,
             data: data,
             success: (res) => {
                 resolve(res);
             },
             error: (error) => {
                 reject(error)
             }
         })
     })
 }
 ​
 getData('./data.json')
     .then((data) => {
     const id = data.id
     return getData('./data2.json', id)
 })
     .then((data) => {
     const userName = data.username
     return getData('./data3.json', userName)
 }).then((data) => {
     console.log(data.company); // => "红鲤鱼与绿鲤鱼与驴公司"
 })

Promise对象还有其他的方法和一些特性,本文不再概述,大家可以查看Promise文档 ,这里推荐阮一峰老师的《ES6入门教程》进行学习。