教你如何手写Promise常用API

catch 方法实现
catch 方法的使用

// 当then为空
let p1 = new Promise((resolve, reject) => {
  reject('失败')
})

p1.then().catch(err => {
  console.log('failed', err)
})

// failed 失败

当 then 为空时,错误被持续抛出到最后一个 then 的 onRejected 函数中,因此 catch 可以理解为then(null, err=>{}) 注意 catch 只有一个 onRejected 函数,onFufilled 为 null。

源码实现

class Promise {
  ...
  catch(errCallback) {
    return this.then(null, errCallback)
  }
}

Promise.resolve,Promise.reject
Promise.resolve 的使用

// 快速创建一个成功的promise
Promise.resolve(123).then(data => {
  console.log(data)
})

// 上述写法等同下面
new Promise((resolve, reject) => {
  resolve(123)
}).then(data => {
  console.log(data)
})

// 特殊情况是,Promise内再套Promise
new Promise((resolve, reject) => {
  resolve(
    new Promise((resolve, reject) => {
      resolve(123)
    })
   )
}).then(data => {
  console.log(data)
})

源码实现

class Promise {
  ...
  let resolve = (value) => {
    // 如果当前value是promise实例,递归调用then
    if(value instanceof Promise) {
      return value.then(resolve, reject)
    }
    if(this.status === PENDING) {
      this.value = value
      this.status = RESOLVED
       this.onResolvedCallbacks.forEach(fn => fn())
    }
  }   
    // Promise.resolve是静态方法
  static resolve(value) {
    return new Promise((resolve, reject) => {
      resolve(value)
    })
  }
	// Promise.reject是静态方法,reject直接就是抛错,不用递归
  static reject(reason) {
     return new Promise((resolve, reject) => {
      reject(reason)
    })
  }
}

finally 方法实现
划重点
finally表示不是最终的意思,而是无论如何都会执行的意思
如果前面是成功,那么 finally 之后继续.then获取成功的数据
如果前面是失败,那么 finally 之后继续.catch捕获失败的错误
如果 finally 返回一个 promise,会等待这个 promise 也执行完毕;如果是失败的 promise,会用它的失败原因传给下一个人

finally 的使用

Promise.resolve(123)
  .finally(() => {
    console.log('finally')
  })
  .then(data => {
    console.log('success', data)
  })
// 输出:finally success 123
Promise.reject(123)
  .finally(() => {
    console.log('finally')
  })
  .catch(err => {
    console.log('failed', err)
  })
// 输出: finally failed 123
Promise.resolve(123)
  .finally(() => {
    console.log('finally')
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        // resolve('ok')
        reject('error')
      }, 1000)
    })
  })
    .then(data => {
    console.log('success', data)
  })
  .catch(err => {
    console.log('failed', err)
  })
// 输出failed error

分析 finally
finally 实际上就是返回了一个then,只是传入的回调函数,无论如何都会被执行

finally 之后可以继续.then,所以不管传入的函数是不是 promise,都得包装成 promise

如果上一个状态是resolve,那么如果 finally 内部 promise 返回的也是 resolve,则忽略,沿用上一轮的 resolve 值

简而言之,如果是 resolve 状态,value 沿用上一次的继续往下传,如果是 reject 状态,不必沿用上一轮的值。

源码实现

class Promise {
  ...
  finally(callback) {
    return this.then(value => {
      return Promise.resolve(callback()).then(() => value)
    },reason => {
      return Promise.resolve(callback()).then(() => { throw reason })
    })
  }
  // Promise.resolve是静态方法
  static resolve(value) {
    return new Promise((resolve, reject) => {
      resolve(value)
    })
  }
}

promisify 的实现
在 nodeJs 中,绝大多数 API 都是采用异步回调的方式,嵌套层级深,比如读取文件,我们每次封装都需要像如下代码这样,套一层 promise,添加错误判断,返回读取的正确值。

const fs = require('fs')
const path = require('path')
function readFile(filename) {
  return new Promise((resolve, reject) => {
    fs.readFile(path.resolve(__dirname, filename), 'utf8', function (err, data) {
      if (err) reject(err)
      resolve(data)
    })
  })
}

promisify 是 utils 工具类里自带的一个方法,可以帮助我们简化步骤,以上代码可以变为下面这样

const fs = require('fs')
const util = require('util')
let read = util.promisify(fs.readFile)
read(path.resolve(__dirname, 'name.txt'), 'utf8').then(data => {})

划重点
由此可见,promisify是一个高阶函数,第一层接收fn,第二层接收fn的参数,接着我们只需要像图1那样,内部包装一个promise既可,还是很简单的
源码解析

// es5function写法,看的清楚点
function promisify(fn) {
  return function (...args) {
    return new Promise((resolve, reject) => {
      fn(...args, function (err, data) {
        if (err) reject(err)
        resolve(data)
      })
    })
  }
}

// 熟练箭头函数的话,可以这么写,es6简化版本 
const promisify = fn => (...args) =>
  new Promise((resolve, reject) => {
    fn(...args, (err, data) => {
      if (err) reject(err)
      resolve(data)
    })
  })
  
// 使用方式
let read = promisify(fs.readFile)
read('name.txt', 'utf8').then(data => {
  console.log(data)
})

all 的实现
all 的基本使用

const fs = require('fs').promises
const path = require('path')

function readFile(filename) {
  return fs.readFile(path.resolve(__dirname, filename), 'utf8')
}
Promise.all([1, readFile('age.txt'), 2, readFile('name.txt'), 3, 4])
  .then(data => {
    console.log(data)
  })
  .catch(err => {
    console.log(err)
  })
//输出 [ 1, '28岁', 2, 'age.txt', 3, 4 ]

使用介绍
all 是 Promise 的静态方法,同 resolve 和 reject
参数接收一个数组,数组里既可以放 promise 也可以放普通值
按照数组里的排列执行顺序,依次执行
只有所有的promise全部成功才算成功,有一个失败就失败

分析样例代码
从上面样例代码,可以看出,Promise.all 返回的是一个 promise,而内部执行数组的时机也应该是并发,所以可以用 for 循环依次执行,而要保证 promsie 执行顺序,我们需要一个计数器,当普通值和 promise 全部执行完毕后,计数器应该和传入的数组长度相等,此时 resolve 结果就可以了

源码实现

static all(promises) {
  return new Promise((resolve, reject) => {
    let index = 0 // 计数器
    let result = []
    const isPromise = (item) => {
      return typeof item.then === 'function'
    }
    const processData = (key, item) => {
      result[key] = item
      if(++index === promises.length) {
        resolve(result)
      }
    }
    for(let i=0,length=promises.length;i<length;i++) {
      let item = promises[i]
      if(isPromise(item)) {
        item.then(data => {
          processData(i, data)
        }, reject)
      } else {
        processData(i, item)
      }
    }
  })
}

all 的缺点
从上述代码可以看到,for 循环并不会因为当前 all 的 promise 被 reject 了而停止,all是不能被中断的,假设我们传入的数组,第 1 个 promise 失败了,实际上,后面的都可以不用再做了,因为一个失败则 Promise.all 就返回失败原因。

race 的实现
race 的基本使用

const fs = require('fs').promises
const path = require('path')

function readFile(filename) {
  return fs.readFile(path.resolve(__dirname, filename), 'utf8')
}
Promise.race([1, readFile('age1.txt'), 2, readFile('name.txt')])
  .then(data => {
    console.log(data)
  })
  .catch(err => {
    console.log(err)
  })
// 输出 1

// 如果当前根目录下没有age1.txt的话,返回错误,因为错误比较快
Promise.race([readFile('age1.txt'), readFile('name.txt')])
  .then(data => {
    console.log(data)
  })
  .catch(err => {
    console.log(err)
  })
// 输出Error: ENOENT: no such file or directory

// 如果当前根目录下有age.txt和name.txt的话,就看谁写读到文件返回了。
Promise.race([readFile('age.txt'), readFile('name.txt')])
  .then(data => {
    console.log(data)
  })
  .catch(err => {
    console.log(err)
  })
// 输出既可能是 age.txt 也可能是 28岁

使用介绍
Promise.race 是赛跑的意思,就是谁快就用谁的返回结果
如果传入的数组里有普通值,就用普通值,否则就看哪个接口先完成,就能哪个接口的返回值
如果某个接口失败了,而失败比较快的话,就返回失败结果。
源码实现

static race(promises) {
  return new Promise((resolve, reject) => {
    const isPromise = (item) => {
      return typeof item.then === 'function'
    }
    for(let i=0,length=promises.length;i<length;i++) {
      let item = promises[i]
      if(isPromise(item)) {
        item.then(resolve,reject)
      } else {
        resolve(item)
      }
    }
  })
}

promise 中断
Promise.race 的一种用法
Promise.race 看似很鸡肋,但我们可以利用它的特性,实现 Promise 的中断
注意,race 和 all 一样,并不是说传入的 promise 返回 reject 了就不走了,for 循环还是会全部执行完的,这里说的中断其实是一种假中断
我们把 promise 中断了,其实只是不要 promise 的返回值了
中断代码实现

let p = new Promise((resolve, reject) => {
  // 构造一个promise,模拟ajax,10妙后返回结果
  setTimeout(() => {
    resolve('成功')
  }, 10000)
})
// 实现一个包装函数
const wrap = promise => {
  let abort
  // 内部构建一个promise,将reject赋予临时变量abort
  let myP = new Promise((resolve, reject) => {
    abort = reject
  })
  const p = Promise.race([promise, myP])
  // 将自己的promise的reject赋给p,用于外部手动中断promise
  p.abort = abort
  return p
}
// 如果promsie返回超过2妙,就中断它,不要返回结果了
let newP = wrap(p)
newP.then().catch(err => {
  console.log(err)
})
setTimeout(() => {
  newP.abort('ajax 超过2秒,超时了')
}, 2000)

代码解析
写一个包装函数 wrap,内部构造一个 promise,将传入 wrap 的 promise 和自己构造的 promise,用 Promise.race 组合一下,将自己构造的 promise 的 reject 方法赋给组合后的 promise 并返回,此时只需手动调用构造的 promise 的 reject 方法,就可以使 Promise.race 返回错误,实现中断的功能

更多JS相关内容欢迎关注在线客服软件海豚客服资深工程师Porschebz

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值