细说JavaScript中的Promise?

对于JavaScript中的Promise大家肯定不会陌生? 那么它具体是怎么操作的呢?让我们大家一起来看看。 首先,promise是一个构造函数,需要传入一个executor执行器,executor会立刻执行,并且传入resolve和reject两个参数,promise有三个状态,fulfilled成功 reject失败 和 pedding等待状态(默认),pedding变成可以fulfilled和reject,fulfilled和reject两个状态不可改变,也可以通过resolve和reject来改变状态,每个promise都有then方法,可以访问成功的原因和失败的原因,当executor发生异常的时候,也会触发promise的失败,看具体的代码

JavaScript 复制代码
const promise = new Promise((resolve, reject) => {
  console.log('executor')
  throw new Error('失败!')
  resolve('success!')
  reject('fail!')
})

promise.then((data) => {
  console.log(data, 'success')
}, (reason) => {
  console.log(reason, 'fail')
})

如果我们自己去写一个Promise来完成上述代码的话,以下步骤:

  1. 创建一个class名字叫做Promise 2 导出module.exports = Promise给其他的模块使用

  2. 里面写一个构造函数constructor带一个参数executor,并且执行executor()

  3. 在constructor新建两个函数resolve(value)成功和失败的函数和reject(reason)并且初始化value和reason变量赋值为undefined ->10. 在executor(resolve, reject)调用自动传入

  4. 在Promise之外新建3个状态的常量 PENDING FUIFLLED REJECT

  5. 在constructor里面初始化状态status

  6. promise写then方法,接受两个参数onFufilled和onReject

  7. 在constructor里面设定两个参数value和reason为undefined,在resolve函数里面判断status是否等于PEDING,如果等于则把状态改为FUIFLLED并把value进行赋值,在reject函数里面判断status是否等于PENDING,如果等于则把状态改为REJECT,把reason进行赋值 注意:只有在状态是pedding的时候 才可以修改状态 和改变成功和失败的原因

  8. 在constructor里面捕获异常,try里面放入执行器executor(resolve, reject)catch里面放入reject(e)函数

  9. then函数里面判断状态,如果是status=== FUIFLLED 调用onFufilled(this.value)函数,如果是status===REJECT,调用onReject(this.reason) // 进行测试;问题, 以上代码都是同步的逻辑,那么有异步代码怎么办?

  10. 问题: promise调用then的时候,可能状态依旧是pedding,那么我们需要将回调先存放起来,等待过一会儿调用resolve时触发 onResolveCallbacks执行,调用reject时触发 onRejectCallbacks执行 具体操作: 因为then方法可以调用多次,所以需要两个数组存放this.onResolveCallbacks=[]和this.onRejectCallbacks=[],并且在then函数 的status===PEDDING时将this.onResolveCallbacks.push(onFufilled),this.onRejectCallbacks.push(onReject) 可以改造成this.onResolveCallbacks.push(() => { onFufilled(this.value) }) this.onRejectCallbacks.push(() => { onReject(this.reason) })方便扩展功能

  11. 在resolve函数中执行上面的callback回调函数this.onResolveCallbacks.forEach(cb => cb()) 在reject函数中执行上面的callback回调函数this.onRejectCallbacks.forEach(cb =>cb()) 具体代码如下:

JavaScript 复制代码
const PENDING = 'PENDING'
const FUIFLLED = 'FUIFLLED'
const REJECT = 'REJECTED'
class Promise {
  constructor(executor) {
    this.status = PENDING
    this.value = undefined // 成功
    this.reason = undefined // 失败
    this.onResolveCallbacks = [] // 成功的回调
    this.onRejectCallbacks = [] // 失败的回调
    const resolve = (value) => {
      if(this.status === PENDING) {
        this.status = FUIFLLED
        this.value = value
        // 成功时调用成功的回调
        this.onResolveCallbacks.forEach(cb => cb())
      }
    }
    const reject = (reason) => {
      if(this.status === PENDING) {
        this.status = REJECT
        this.reason = reason
        // 成功时调用成功的回调
        this.onRejectCallbacks.forEach(cb =>cb())
      }
    }
    try {
      executor(resolve, reject)
    } catch(e) {
      reject(e)
    }
  }
  then(onFufilled, onReject) {
    if(this.status === FUIFLLED) {
      onFufilled(this.value)
    }
    if(this.status === REJECT) {
      onReject(this.reason)
    }
    if(this.status === PENDING) {
      this.onResolveCallbacks.push(() => {
        onFufilled(this.value)
      })
      this.onRejectCallbacks.push(() => {
        onReject(this.reason)
      })
    }
  }
}

module.exports = Promise

那么如何解决我们常用的嵌套地狱,进行链式调用呢?继续往下看一个场景

JavaScript 复制代码
const fs = require('fs')
const path = require('path')
fs.readFile(path_resolve(__dirname, 'a.txt'), 'utf-8', function(err, data) => {
  if(err) return console.log(err)
  fs.readFile(data, 'utf-8', function(err, data) {
    if(err) return console.log(err)
    console.log(data)
  })
})

上面的代码容易造成一个问题,函数嵌套多的话造成回调地狱问题,我么可以新增一个promise来解决上述问题,改成为

JavaScript 复制代码
function readFile(filepath) {
  let promise = new Promise((resolve, reject) => {
    fs.readFile(filepath, 'utf-8', function(err, data) {
      if(err) return reject(err)
      resolve(data)
    })
  })
  return promise
}

readFile(path.resolve(__dirname, 'a.txt')).then((data) => {
}, err => {
})

具体步骤解释为: 1. 建立函数readFile接收一个文件路径参数 ->4. 在函数里面调用fs.readFile读取 3. 在外面调用readFile函数,传递一个路径参数进去,并链式调用then 4. 因为readFile里面链式调用then,所以readFile函数内部必须新建promise并返回出去,把fs.readFile放入到promise内部 5. 改造fs.readFile回调函数,如果有err则reject(err),读取成功则用resolve(data)

以上方法有一个问题,readFile单独写一个,那么我如果要写一个writeFile又得重新写一个函数,这样做太麻烦了,那么要怎么解决这个问题呢?

JavaScript 复制代码
const { promisify } = require('util')
let readFile = promisify(fs.readFile) // 高阶函数

这样异步函数就帮我们转换成promise的形式了,它的返回值是一个函数 那么promisify它到底是怎么实现的呢?看下列代码,其实在外面包装了一个高阶函数

JavaScript 复制代码
function promisify(fn) {  // fn ====> fs.readFile
  return function(...args) { // readFile
    let promise = new Promise((resolve, reject) => {
      fn(...args, function(err, data) {
        if(err) return reject(err)
        resolve(data)
      })
    })
    return promise
  }
}
let readFile = promisify(fs.readFile) // 高阶函数
readFile(path.resolve(__dirname, 'a.txt'), 'utf8').then((data) => {
  console.log(data) // aaaaaa
}, err => {
})

执行成功了! 看下面案例,将异步调用拍平化!

JavaScript 复制代码
// 1) then链的特点,当then中成功和失败的回调函数返回的是一个promise,内部会解析这个promise,并且将结果传递
// 到外层的下一个then中
// 2) 下一次then成功还是失败,取决于当前promise状态
// 3) 如果成功和失败返回是不是一个promise,那么这个结果会直接传递到下一个人的成功
// 4) 如果成功和失败的回调中抛出异常 则会执行下一个then的失败
readFile(path.resolve(__dirname, './a.txt'), 'utf8').then((data) => {
  return readFile(data+'.txt', 'utf8')
}).then((data) => {
  console.log('data', 'success')
}, (err) => {
  console.log(err, 'fail')
  return true
}).then((data) => {
  console.log(data, 'success')
  throw new Error('错误')
}).then(() => {}, (err) => {
  console.log(err)
})
// 总结: 让promise(then)失败有两种方式 一种是抛异常 返回一个失败的promise

写一个案例,如何实现下列的代码逻辑

JavaScript 复制代码
let promise = new Promise((resolve, reject) => {
  resolve(100)
})

promise.then((data) => {
  return data
}).then((data) => {
  console.log(data, 'success')
})

于是可以改造上面then方法的代码

JavaScript 复制代码
  let promise2 = new Promise((resolve, reject) => {
      if(this.status === FUIFLLED) {
        try {
          let x = onFufilled(this.value)
          resolve(x)
        } catch (e) {
          reject(e)
        }
      }
      if(this.status === REJECT) {
        try {
          let x = onReject(this.reason)
          resolve(x)
        } catch (e) {
          reject(e)
        }
      }
      if(this.status === PENDING) {
        this.onResolveCallbacks.push(() => {
          try {
            let x = onFufilled(this.value)
            resolve(x)
          } catch (e) {
            reject(e)
          }
        })
        this.onRejectCallbacks.push(() => {
          try {
            let x = onReject(this.reason)
            resolve(x)
          } catch (e) {
            reject(e)
          }
        })
      }
    })
    return promise2

思路步骤如下:

  1. 在then方法新建一个promise((resolve, reject))
  2. 在then的状态为pending中resolve成功函数里面返回一个普通值并且接受resolve(返回的值),方便在下一个then链式当中成功函数获取
  3. 在then的状态为pending中reject成功函数里面返回一个普通值并且接受resolve(返回的值),方便在下一个then链式当中成功函数获取
  4. 如果是出错了,则增加try catch 来捕获异常

那么问题又来了,如果then里面返回是一个promise怎么办? 解决步骤:

  1. 新建一个函数resolvePromise并且接收四个参数
  2. 注意点,取不到promise2的值,因为是同步代码,先执行,然后在返回promise2,怎么解决呢? 在里面包一个setTimeout,因为promise里面先执行,所以可以获取promise2
  3. 传递过来的promise需要兼容别人写的和自己写的,所以需要判断promise的类型 完整版代码如下:
JavaScript 复制代码
const PENDING = 'PENDING'
const FUIFLLED = 'FUIFLLED'
const REJECT = 'REJECTED'

function resolvePromise(promise2, x, resolve, reject) {
  // console.log(promise2, x, resolve, reject)
  // 如果x和promise 引用的是同一个对象,那么promise2 要等待x执行完毕
  // x是一个promise,而且永远不会成功和失败,那么就在这里等待
  if(x === promise2) return reject(new TypeError('出错了'))

  // 我如何知道x是不是promise
  if((typeof x === 'object' && x !== null) || typeof x === 'function') {
    // 有可能是promise
    let called = false
    try {
      let then = x.then // 取then的时候会报错 直接失败 第34 行
      if(typeof then === 'function') {
         then.call(x, (y) => {
          // 为了防止promise解析后的结果依然是promise,所以需要递归解析
          if(called) return
          called = true
          resolvePromise(promise2, y, resolve, reject)
         }, (r) => {
          if(called) return
          called = true
          reject(r)
         })
      } else {
        resolve(x)
      }
    } catch(e) {
      if(called) return
      called = true
      reject(e)
    }
  } else {
    resolve(x) // 普通值 直接将结果
  }
}

class Promise {
  constructor(executor) {
    this.status = PENDING
    this.value = undefined // 成功
    this.reason = undefined // 失败
    this.onResolveCallbacks = [] // 成功的回调
    this.onRejectCallbacks = [] // 失败的回调
    const resolve = (value) => {
      if(this.status === PENDING) {
        this.status = FUIFLLED
        this.value = value
        // 成功时调用成功的回调
        this.onResolveCallbacks.forEach(cb => cb())
      }
    }
    const reject = (reason) => {
      if(this.status === PENDING) {
        this.status = REJECT
        this.reason = reason
        // 成功时调用成功的回调
        this.onRejectCallbacks.forEach(cb =>cb())
      }
    }
    try {
      executor(resolve, reject)
    } catch(e) {
      reject(e)
    }
  }
  then(onFufilled, onReject) {
    onFufilled = typeof onFufilled === 'function' ? onFufilled : v => v
    onReject = typeof onReject === 'function' ? onReject : e => {throw e}
    let promise2 = new Promise((resolve, reject) => {
      if(this.status === FUIFLLED) {
        setTimeout(() => {
          try {
            let x = onFufilled(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }
      if(this.status === REJECT) {
        setTimeout(() => {
          try {
            let x = onReject(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }
      if(this.status === PENDING) {
        this.onResolveCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFufilled(this.value)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        })
        this.onRejectCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onReject(this.reason)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        })
      }
    })
    return promise2
  }
}

module.exports = Promise

怎么判断上面的promise代码是否符合规范呢,有一个包可以帮助我们检测promise-aplus-tests 安装包 npm install promise-aplus-tests -g 具体代码

JavaScript 复制代码
Promise.deferred = function() {
  let dfd = {}
  dfd.promise = new Promise((resolve, reject) => {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}

执行指令 promise-aplus-tests 当前路径 完成成功!

相关推荐
如若12310 分钟前
对文件内的文件名生成目录,方便查阅
java·前端·python
滚雪球~1 小时前
npm error code ETIMEDOUT
前端·npm·node.js
沙漏无语1 小时前
npm : 无法加载文件 D:\Nodejs\node_global\npm.ps1,因为在此系统上禁止运行脚本
前端·npm·node.js
supermapsupport1 小时前
iClient3D for Cesium在Vue中快速实现场景卷帘
前端·vue.js·3d·cesium·supermap
brrdg_sefg1 小时前
WEB 漏洞 - 文件包含漏洞深度解析
前端·网络·安全
胡西风_foxww1 小时前
【es6复习笔记】rest参数(7)
前端·笔记·es6·参数·rest
m0_748254881 小时前
vue+elementui实现下拉表格多选+搜索+分页+回显+全选2.0
前端·vue.js·elementui
星就前端叭2 小时前
【开源】一款基于Vue3 + WebRTC + Node + SRS + FFmpeg搭建的直播间项目
前端·后端·开源·webrtc
m0_748234522 小时前
前端Vue3字体优化三部曲(webFont、font-spider、spa-font-spider-webpack-plugin)
前端·webpack·node.js
Web阿成2 小时前
3.学习webpack配置 尝试打包ts文件
前端·学习·webpack·typescript