Promise规范这么多细节,你真的了解吗? | 【javascript基础系列】

342 阅读9分钟

本文正在参加「金石计划 . 瓜分6万现金大奖」

前言

这是一篇讲解Promise的文章,相当于自己对Promise的再次学习与自我总结

请点击Script 食用 【看完文章再来看demo】

code.juejin.cn/pen/7163892…

为什么要用Promise

  • 在Promise 之前解决异步的方式就是回调函数,当有多个 回调函数的时候,就形成了我们经常说的 回调地狱

  • 回调函数非常难维护,因此社区出现了很多替代的解决方案,其实就是 类似于 Promise的形式,在ES6中 正式 推出了 Promise,但,这次是原生的代码

  • Promise的好处是 链式调用,好维护,代码阅读性强

promise 的基本用法

基本代码示例

let p = new Promise((resolved,reject)=>{
    resolved('成功')
})

三个状态

  • 三个状态 pendding onFulfilled 和 onRejected
  • 状态从 pendding 到 onFulfilled 或者 onRejected,一旦状态改变,不可逆
  • 通过调用resolved(),来将状态 变成 onFulfilled,通过调用reject(),将状态变成onRejected

拒绝的情况可以分为两种

  • 通过then方法的第二个函数 p.then(fun1,func2=>{此处是 拒绝状态的回调})
    let p8 = new Promise((resolve,reject)=>{
        setTimeout(()=>{
            // resolve('成功  == p8')
            reject('失败  == p8')
        })
    })
    // 获取 reject 方式一
    p8.then(null,function(err){
        console.log('err === 方式一',err)
    })
    
  • 通过then的catch方法 p.catch(e=>{此处是 拒绝状态的回调}).then()
    // 获取 reject 方式二
    p8.then(function(res){
        console.log('res ===',res)
    }).catch(e=>{
        console.log('err === 方式二',e)
    })
    

catch的摆放位置也是很有讲究

解析

Promise.prototype.catch()方法是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

catch在then前面

  • 其实相当于这个 .then(null, rejection)
  • **但是如果后面再接 .then,那么其实 后面的 then 是下一个 Promise的then,而不是前面Promise的then
// demo9 catch在then的前面
let p9 = new Promise((resolve,reject)=>{
   setTimeout(()=>{
       // resolve('成功  == p9')
       reject('失败  == p9')
   })
})
p9.catch(err=>{
   // catch在前面 可以 捕获 reject 和 promise内部的抛错(其实抛错最终也是 走reject的形式)
   console.log('err ====',err)
}).then(function(res){
   // 其实此处 已经是 catch 之后,又返回了 另外一个 promise的执行,并不是 p9,你要搞清楚
   console.log('res ===',res)
},function(err){
   console.log('err === then',err)
})

catch在then后面

// demo10 catch在then的后面
let p10 = new Promise((resolve,reject)=>{
   setTimeout(()=>{
       // resolve('成功  == p10')
       reject('失败  == p10')
   })
})
p10.then(function(res){
   console.log('res ===',res)
}).catch(err=>{
   // 注意此处 上面 then 第二个 函数必须没有,才会进入到 此处的catch 里面
   console.log('err ====',err)
})

catch 捕获的错误 只能捕获同步的代码中的错误

let p9 = new Promise((resolve,reject)=>{
   // throw new Error('messgae ===') // 此处的错误是能捕获到,相当于 reject 出去了
   setTimeout(()=>{
       throw new Error('messgae ===')  // 此处的错误是异步中的,后面的 catch 和 err函数 无法捕获到
       // resolve('成功  == p9')
       // reject('失败  == p9')
   })
})
p9.then(function(res){
   console.log('res ===',res)
},function(err){
   console.log('err === then',err)
})

promise中的错误捕获问题

同步代码中的报错

同步代码中的报错会被后面的catch捕获,并最终以 reject(Error)的形式结束状态

异步代码中的报错

异步代码中的报错是无法被捕获的,会影响在它生错误之后的 事件的代码执行

示例代码

let p_resolve = new Promise((resolve,reject)=>{
  // throw new Error('messgae ===') // 此处的错误是能捕获到,相当于 reject 出去了
  setTimeout(()=>{
      // throw new Error('messgae ===')  // 此处的错误是异步中的,后面的 catch 是无法捕获的
      resolve('成功  == p_resolve')
      // reject('失败  == p10')
  })
})
let p_reject = new Promise((resolve,reject)=>{
  // throw new Error('messgae ===') // 此处的错误是能捕获到,相当于 reject 出去了
  setTimeout(()=>{
      // throw new Error('messgae ===')  // 此处的错误是异步中的,后面的 catch 是无法捕获的
      // resolve('成功  == p10')
      reject('失败  == p_reject')
  })
})

如何将异步函数封装成Promise

这个其实很简单,我们来一个 现实中的例子就可以了,ajex的网络请求

function ajaxAsync(url) {  
return new Promise(function(resolve, reject){

var client = new XMLHttpRequest(); client.open("GET", url); client.onreadystatechange = function() {

if (this.readyState !== 4) { return;

}  
if (this.status === 200) {

resolve(this.response);
} else {  
reject(new Error(this.statusText));

} };

client.send(); });

};

ajax('/ajax.json') .catch(function() {

console.log('失败'); })

.then(function() { console.log('成功');

})

Promise的规范解读

Promise有自己规范,只要是 符合规范的 代码对象,都可以是 Promise,你也可以自己创建自己的Promise

Promise 规范的地址

promisesaplus.com/

相关要求

  • 对状态的要求
    • Pending
    • Fulfilled
    • Rejected
  • 必须有一个then 方法
    • onFulfilled 方法的要求
      • 必须是异步调用
      • 必须是普通函数
      • 只能被调用一次
      • 不可以在 状态改变成 成功之前调用
    • onReject 方法的要求
      • 必须是异步调用
      • 必须是普通函数
      • 只能被调用一次
      • 不可以在 状态改变成 失败 之前调用
    • then 最后返回的也必须是一个 后续的Promise,具体规则如下 【重点&难点】
      • then接受两个参数,onFulfilled 和 onRejected,必须是函数,如果不是函数则忽略

      • onFulfilled - 如果存在,那么接受一个参数,就是 Promise 返回成功的 值

      • onRejected - 如果存在,那么接受一个参数,就是 Promise 返回失败的原因

      • onFulfilled 与 onRejected 方法必须异步调用

      • onFulfilled 与 onRejected 方法必须是一个普通函数

      • then可以被 同一个Promise 调用多次 参考demo1 【最下方有代码集合】

      • then返回一个新的 Promise,具体的处理规则比较复杂,如下[重点&难点]

        • 如果then 接受的 onFulfilled 和 onRejected 不是函数(比如是一个对象,一个字符串),那么就是将 上一个 Promise的结果与状态 原封不动的 传递给 下一个 Promise 参照demo2
        • 如果then接受的 onFulfilled 和 onRejected 是函数,取决于 函数调用的返回值,即的返回值(result)
          • 如果 result 是一个 Promise 参照demo3
            • 那么就将 此Promise 的状态和结果传递给下一个 Promise
          • 如果 result 是一个 对象
            • 看result 的内部,如果result对象有 then方法
              • 那么 会直接调用 then 方法,并把 (onFulfilled,与onRejected)作为参数传入,最后的状态取决于 最后的函数调用。如果onfulfilled,那么就是status = Fulfilled 并将执行的结果,返回给 下一个Promise 如果调用 onRejected,那么就是 status = Rejected 并将执行的结果,返回给下一个 Promise 参照demo4
            • 看result 的内部,如果result对象没有 then方法
              • 那么直接将 result 作为结果返回到下一个Promise,状态为 Fulfilled 参考demo5
          • 如果 result 是一个函数 同对象的处理原则一样
          • 其余(不是函数与对象) demo2
            • 将上一个Promise的status 和 结果 传递给下一个 Promise
          • 如果then的调用函数里面报错了demo6
            • status = Rejected 并将执行的结果,发送给下一个 Promise

Promise的其他方法

Promise.resolve

相当于 new Promise(resolve,reject=>reject())

Promise.reject

相当于 new Promise(resolve=> resolve())

Promise.all

必须所有的 promise 都成功了,才算成功,只要有一个失败就失败了

Promise.race

竞赛,第一个出结果的 Promise 的状态 决定 整个的 状态,如果第一个成功,那么就是成功,第一个失败,那就是失败

Promise.finally

不管Promise 是成功还是失败,只要状态改变了,都会走这个方法,只是不知道 状态是什么

Promise.allSetted

必须所有的状态都返回了,才算结束;返回的值里面包含每一个的 状态和值

 [ { status: 'fulfilled', value: 42 }, { status: 'rejected', reason: -1 } ] 

如何手写一个Promise

  • 我想在了解了Promise的相关规范和细节,实现个 简单的版本问题不大;
  • 但是如果你是死记硬背的,那么隔几天就忘了,意义不大
  • 掌握手写一个Promise的意义在于 了解Promise的规范和很多的细节
  • 时间有限,后续再来完善

相关代码集合

// 主要演示,Promise的then处理的规则展示

let pro = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        // resolve('111-Fulfilled')
        reject('222 - Rejected')
    })
})

// demo1 - 演示: 同一个Promise 可以多次调用
// pro.then(res=>{
//     console.log('第一次调用 res ===',res)
// },err=>{
//     console.log('第一次调用 err ===',err)
// })
// pro.then(res=>{
//     console.log('第二次调用 res ===',res)
// },err=>{
//     console.log('第二次调用 err ===',err)
// })

// demo2 - 演示: 如果then 接受的 onFulfilled 和 onRejected 不是函数(比如是一个对象,一个字符串),那么就是将 上一个 Promise的结果与状态 原封不动的 传递给 下一个 Promise
// let next_pro =  pro.then('不是函数','不是函数')
// next_pro.then(res=>{
//     console.log('第二次调用 res ===',res)
// },err=>{
//     console.log('第二次调用 err ===',err)
// })

// demo3 - 演示: 如果then 接受的 onFulfilled 和 onRejected 是函数-函数的返回值是 promise
// let next_pro =   pro.then(res=>{
//         return new Promise((resolve,rejected)=>resolve('next_pro 的状态取决于我 【resolve】'))
//      },err=>{
//          return new Promise((resolve,rejected)=>rejected('next_pro 的状态取决于我 【rejected】'))
//      })
// next_pro.then(res=>{
//     console.log('next_pro的状态取决于 返回的 Promise res ===',res)
// },err=>{
//     console.log('next_pro的状态取决于 返回的 Promise err ===',err)
// })

// demo4 - 演示: 如果then 接受的 onFulfilled 和 onRejected 是函数-函数的返回值是 一个对象,主要看对象有没有 then 方法
// let next_pro =   pro.then(res=>{
//     return {
//         then:((resolve,rejected)=>resolve('next_pro 的状态取决于then方法 【resolve】'))
//     }
// },err=>{
//     return{
//         then:((resolve,rejected)=>rejected('next_pro 的状态取决于 then方法 【rejected】'))
//     }
// })
// next_pro.then(res=>{
// console.log('next_pro的状态取决于 返回的 返回对象的 then 方法 res ===',res)
// },err=>{
// console.log('next_pro的状态取决于 返回的 返回对象的 then 方法 err ===',err)
// })

// demo5 - 演示: 如果then 接受的 onFulfilled 和 onRejected 是函数-函数的返回值是 一个对象,主要看对象有没有 then 方法
// 此处演示没有then方法,就是返回一个普通对象 【相当于 resolve了过去】
// let next_pro =   pro.then(res=>{
//     return {
//         a:1,
//         nothen:((resolve,rejected)=>resolve('next_pro 的状态取决于then方法 【resolve】'))
//     }
// },err=>{
//     return{
//         a:2,
//         nothen:((resolve,rejected)=>rejected('next_pro 的状态取决于 then方法 【rejected】'))
//     }
// })
// next_pro.then(res=>{
// console.log('next_pro的状态取决于 返回的 返回对象的 then 方法 res ===',res)
// },err=>{
// console.log('next_pro的状态取决于 返回的 返回对象的 then 方法 err ===',err)
// })

// demo6 - 演示: 如果then 接受的 onFulfilled 和 onRejected 是函数-函数,函数执行过程中报错了,那么相当于 reject
// let next_pro =   pro.then(res=>{
//     throw new Error('报错了 in res')
// },err=>{
//     throw new Error('报错了 in err')
// })
// next_pro.then(res=>{
// console.log('next_pro的状态 【上一步then方法报错了,直接是 reject】 res ===',res)
// },err=>{
// console.log('next_pro的状态 【上一步then方法报错了,直接是 reject】 err ===',err)
// })



总结

  • 虽然我们每天都在使用Promise,但是,恐怕很多细节都没有掌握
  • 虽然只需要掌握常用的方法,就能在满足日常的开发使用,但是 当我们了解了很多的细节,那么当遇到问题的时候就不会慌,很容易排查错误