Promise重学—实战封装

1,034 阅读5分钟

这是我参与更文挑战的第7天,活动详情查看: 更文挑战

为加强巩固对Promise的理解,学习封装一个属于自己的Promise类库,

话接上文,如果大家对Promise的基础知识还是不太清楚的,可以先看看这篇Promise重学-基础知识篇

1, Promise类库封装

class Promise {
    // 构造方法
    constructor(executor) {
      // 声明构造函数
      // 添加属性
      this.PromiseState = 'pending'
      this.PromiseResult = null
      // 声明属性
      this.callbacks = [];
      // 保存实例对象的this的值
      const self = this;
      function resolve(data) {
            // 判断状态,状态只能修改一次
            if (self.PromiseState !== 'pending') return ;
            // 修改对象状态  PromiseState
            self.PromiseState = 'fulfilled';
            // 设置对象结果值 PromiseResult
            self.PromiseResult = data;
            // 调用成功的回调函数
            setTimeout(() => {
                self.callbacks.forEach(item => {
                    item.onResolved(data)
                })
            })
      }
      function reject(data) {
            // 判断状态,状态只能修改一次
            if (self.PromiseState !== 'pending') return ;
            // 修改对象状态  PromiseState
            self.PromiseState = 'rejected'
            // 设置对象结果值 PromiseResult
            self.PromiseResult = data
            // 调用失败的回调函数
            setTimeout(()=> {
                self.callbacks.forEach(item => {
                    item.onRejected(data)
                })
            })
      }
      try {
            // 同步调用
            executor(resolve, reject)
      } catch (error) {
           reject(error)
      }
    }
    // then 方法封装
    then(onResolved, onRejected) {
        const self = this;
        // 判断回调函数参数
        if (typeof onRejected !== 'function') {
            onRejected = reason => {
                throw reason;
            }
        }
        if (typeof onResolved !== 'function') {
            onResolved = value => value;
        }
        return new Promise((resolve, reject) => {
            // 封装函数
            function callback(type) {
                try {
                   // 获取回调函数的执行结果
                   let result = type(self.PromiseResult)
                   // 判断
                   if(result instanceof Promise) {
                       // 如果是promise类型对象
                       result.then(v => {
                          resolve(v)
                       }, r => {
                          reject(r)
                       })
                   } else {
                       // 结果的对象状态改为成功
                       resolve(result)
                   }
                } catch(e) {
                    reject(e)
                }
            }
            // 调用回调函数
            if(this.PromiseState === 'fulfilled') {
                setTimeout(()=> {
                    callback(onResolved)
                })
            }
            if(this.PromiseState === 'rejected') {
                setTimeout(()=> {
                    callback(onRejected)
                })
            }
            if(this.PromiseState === 'pending') {
              // 保存回调函数
              this.callbacks.push({
                  onResolved: function() {
                      callback(onResolved)
                  },
                  onRejected: function() {
                      callback(onRejected)
                  }
              })
            }
        })
    }
    // catch 方法封装
    catch(onRejected) {
        return this.then(undefined, onRejected)
    }
    // resolve方法
    static resolve(value) {
        return new Promise((resolve, reject) => {
            if (value instanceof Promise) {
                value.then(v => {
                    resolve(v)
                }, r=> {
                    reject(r)
                })
            } else {
                resolve(value)
            }
        })
     }
     // reject 方法封装
    static reject(reason) {
        return new Promise((resolve, reject) => {
            reject(reason)
        })
    }
    // all 方法封装
    static all(promise) {
        let count = 0;
        let arr = [] // 保存成功的结果
        return new Promise((resolve, reject) => {
            for(let i=0; i<promise.length; i++) {
                promise[i].then(v=>{
                      // 每个promise对象都成功
                      count++;
                      arr[i] = v;
                      if(count === promise.length) {
                          resolve(arr)
                      }
                }, r=>{
                    reject(r)
                })
            }
        })
    }
    // race 方法封装
    static race(promise) {
        return new Promise((resolve,reject) => {
            for(let i=0;i<promise.length; i++) {
                promise[i].then(v=> {
                    resolve(v)
                },r=> {
                    reject(r)
                })
            }
        })
    }
}

测试代码

let p = new Promise((resolve, reject) => {
        setTimeout(()=> {
           reject('ok')
        }, 100)
    })
const res = p.then(value => {
        console.log(value)
    }, reason=> {
        console.warn(reason)
    })
    console.log(res)
const res2 = p.catch(reason => {
    console.log(reason)
})
    console.log(res2)


let p4 = new Promise((resolve, reject) => {
    resolve('ok')
    console.log(111)
})
    p4.then(value => {
        console.log(222)
    })
    console.log(333)

注意: 回调函数里面的代码是异步执行的,

大家可以看看下面的代码执行结果:

let p = new Promise((resolve, reject) => {
    resolve('ok')
    console.log(111)
})
    p.then(value => {
        console.log(222)
    })
    console.log(333)

答案是: 111 —> 333 —> 222

同步代码先执行,异步代码需要等到同步代码执行完之后才开始执行

2, async 函数

1,函数的返回值为promise对象

2, promise对象的结果由async函数执行的返回值决定

3, await 表达式

1, await右侧的表达式一般为promise对象,但也可以是其他的值

2,如果表达式是promise对象,await返回的是promise成功的值

3,如果表达式是其他的值,直接将此值作为await的返回值

注意: await 必须写在async函数中, 但是async函数中可以没有await

注意:如果await的promise失败了,就会抛出异常,需要通过try…catch捕获

async function main() {
    let p = new Promise((resolve, reject) => {
        resolve('ok')
    })
    // 1 右侧为promise的情况
    let res = await p;
    // 2, 右侧为其他类型的数据
    let res2 = await 20;
    // 如果promises是失败的状态, 上面代码把reject('error')注释掉了,可以切换过来看效果
    try {
        let res3 = await p
    } catch(e) {
        console.log(e)
    }
}
 main()

4, async 和 await 结合

/**
 * 读取多个文件内容,并拼接
 */
const fs = require('fs')
const util = require('util')
const mineReadFile = util.promisify(fs.readFile)
async function myReadFile() {
    // 读取第一个文件
    try {
        let data1 = await mineReadFile('./package/content1.txt')
        let data2 = await mineReadFile('./package/content2.txt')
        let data3 = await mineReadFile('./package/content3.txt')
        console.log(data1 + data2 + data3)
    }catch(e) {
       console.log(e)
    }
}
mineReadFile()
/**
 * 封装AJAX接口请求
 * @param {*} url 
 */
function sendAjax(url) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()
        xhr.open('GET', url);
        xhr.send()
        xhr.onreadystatechange = function() {
            if(xhr.readyState === 4) {
                if(xhr.status >=200 && xhr.status < 300) {
                    resolve(xhr.response)
                } else {
                    reject(xhr.status)
                }
            }
        }
    })
}

xxxx.addEventListener('click', async function() {
    let content = await sendAjax('https://api.apiopen.top/getJoke')
    console.log(content)
})

5, 说说AJAX,AXIOS, FETCH的区别

1,AJAX:异步网络请求,它实现了页面可以无刷新的请求数据。以往,页面表单提交数据,在用户点击完”submit“按钮后,页面会强制刷新一下,体验十分不友好。

var request = new XMLHttpRequest(); // 创建XMLHttpRequest对象
//ajax是异步的,设置回调函数
request.onreadystatechange = function () { // 状态发生变化时,函数被回调
    if (request.readyState === 4) { // 成功完成
        // 判断响应状态码
        if (request.status === 200) {
            // 成功,通过responseText拿到响应的文本:
            return success(request.responseText);
        } else {
            // 失败,根据响应码判断失败原因:
            return fail(request.status);
        }
    } else {
        // HTTP请求还在继续...
    }
}
// 发送请求:
request.open('GET', '/api/categories');
request.setRequestHeader("Content-Type", "application/json") //设置请求头
request.send();//到这一步,请求才正式发出

2,AXIOS:axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,本质上也是对原生XHR的封装,只不过它是Promise的实现版本,符合最新的ES规范

  • 从浏览器中创建 XMLHttpRequests
  • 从 node.js 创建 http 请求
  • 支持 Promise API
  • 拦截请求和响应
  • 转换请求数据和响应数据
  • 取消请求
  • 自动转换 JSON 数据
  • 客户端支持防御 XSRF

因为axios设计简洁,API简单,支持浏览器和node,

3,FETCH:fetch是前端发展的一种新技术产物

  • 当接收到一个代表错误的 HTTP 状态码时,从 fetch()返回的 Promise 不会被标记为 reject, 即使该 HTTP 响应的状态码是 404 或 500。相反,它会将 Promise 状态标记为 resolve (但是会将 resolve 的返回值的 ok 属性设置为 false ),仅当网络故障时或请求被阻止时,才会标记为 reject。
  • 默认情况下,fetch 不会从服务端发送或接收任何 cookies, 如果站点依赖于用户 session,则会导致未经认证的请求(要发送 cookies,必须设置 credentials 选项)。

​ fetch代表着更先进的技术方向,但是目前兼容性不是很好,在项目中使用的时候得慎重。