乐趣区

手撸-Promise

手撸 Promise

  • Promise 作为 ES6 的核心内容,是前端童鞋必备的基础知识!更是面试通关的必刷题!
  • Promise 的出现,解决了 ” 蛮荒时代 ” 的回调地狱,让 js 异步 callback 走向简洁,优雅!
  • 本文参照 Promise/A+ 实现(不足之处,请留言指出)

第一版(初出茅庐)

状态
  • Promise 有三种状态值标识当前的异步状态!
  • pending 状态可变为 fulfilled 或 rejected
  • 一旦处于 fulfilled 或 rejected 时 则不可改为其他状态
const PENDING = 'pending' // 待定,等待
const FULFILLED = 'fulfilled' // 成功
const REJECTED = 'rejected' // 失败

promise 的基本实现

  • promise 构造函数、resolve 方法、reject 方法、then 方法
// promise 构造函数
function Promise(executor) {
  this.state = 'pending'
  this.value = null // 当状态 fulfilled,给定一个 value 值
  this.reason = null // 当状态 rejected,给定一个失败原因
  
  // resolve 方法
  function resolve(value) {if (this.status === PENDING) {
      this.status = FULFILLED
      this.value = value
    }
  }
  
  
  // reject 方法
  function reject(reason) {if (this.status === PENDING) {
      this.status = REJECTED
      this.reason = reason
    }
  }

  try {executor(resolve, reject)
  } catch (e) {reject(e)
  }
}
  • then 方法

当状态为 fulfilled 时调用 onFulfilled, 当状态为 rejected 时调用 onRejected


Promise.prototype.then =  function(onFulfilled, onRejected) {if(this.state === FULFILLED){onFulfilled(this.value)
    }
    if(this.state === FULFILLED){onFulfilled(this.reason)
    }
}

此时基本有了 promise 的样子,但 Promise 真正要解决的异步问题,并未处理!

第二版 克敌(异步)制胜

  • 头脑(浆糊)风暴,思考 then 方法是同步调用,那必然会在异步任务完成前执行,该如何处理呢?

此时次刻,聪明的你必然想到了订阅发布,先通过 then 订阅异步成功后要执行的任务,在 resolve 或 reject 调用后执行已订阅的任务

// 给 Promise 中添加两个事件池,存储订阅事件

function Promise(executor) {
  ......
  this.onFulfilledCallBack = [] // onFulfilled 订阅池
  this.onRejectedCallBack = [] // onRejected 订阅池
  ......

// resolve 方法、reject 方法

  function resolve(value) {if (self.status === PENDING) {
      self.status = FULFILLED
      self.onFulfilledCallBack.forEach(cb => cb(self.reason))
    }
  }

  function reject(reason) {if (self.status === PENDING) {
      self.status = REJECTED
      self.reason = reason
      self.onRejectedCallBack.forEach(cb => cb(self.reason))
    }
  }

// 将 onFulfilled, onRejected 放入事件池中

Promise.prototype.then =  function(onFulfilled, onRejected) {if(this.state === FULFILLED){onFulfilled(this.value)
    }
    if(this.state === FULFILLED){onFulfilled(this.reason)
    }
    if(this.state === PENDING){this.onFulfilledCallBack.push(onFulfilled)
        this.onRejectedCallBack.push(onRejected)
    }
}

此时此刻,恭喜你正式成为一名江湖(江湖)剑客,可以行侠仗义了

带三版 千秋万代一统江湖 -then 方法的链式调用

  • 根据 Promise/A+ 规范,then 方法返回一个新的 promise
Promise.prototype.then = function(onFulfilled, onRejected) {let promise2 = new Promise((resolve, reject) => {if (this.state === FULFILLED) {let x = onFulfilled(this.value);
        resolvePromise(promise2, x, resolve, reject);
      };
      if (this.state === REJECTED) {let x = onRejected(this.reason);
        resolvePromise(promise2, x, resolve, reject);
      };
      if (this.state === PENDING) {this.onFulfilledCallBack.push(()=>{let x = onFulfilled(this.value);
          resolvePromise(promise2, x, resolve, reject);
        })
        this.onRejectedCallBack.push(()=>{let x = onRejected(this.reason);
          resolvePromise(promise2, x, resolve, reject);
        })
      }
    })
    return promise2
}
  • 注意 resolvePromise 方法,我在调用事件池里的方法后,为什么要 resolvePromise 方法将新的 promise2 与回调方法的返回值通过 resolvePromise 处理呢???
  • 思考:若事件池内的方法(then 中用户传入的回调方法),返回的也是一个 promise 我们处理逻辑又将如何执行
  • 根据示例:观察原生 Promise
let test = new Promise((resolve, reject) => {setTimeout(() => {console.log(1)
    resolve(1)
  }, 1000)
})
test
  .then(data => {console.log('data:', data)
    return new Promise((resolve, reject) => {setTimeout(() => {console.log(2)
        resolve(2)
      }, 2000)
    }).then(data => {console.log('data:', data)
      console.log(3)
      return 3
    })
  })
  .then(data => {console.log('data:', data)
    console.log(4)
  })

// 输出结果
1
data: 1
2
data: 2
3
data: 3
4
  • 据此我们可以发现外层的 then 回调会在内层的 promise 的 then 回调全部执行完才会执行,那内外层的 promise 必然有着不可告人的秘密,他们就是通过 resolvePromise 方法“勾结”在一起
resolvePromise 方法
  • 参数有 promise2(then 默认返回的 promise)、x(then 回调中 return 的对象)、resolve、reject

// 这里先给出完整的 resolvePromise 代码,看完后我们来思考几个问题

function resolvePromise(promise2, x, resolve, reject){
  // 处理循环引用
  if(x === promise2){
    // reject 报错
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  // 防止多次调用
  let called;
  if (x instanceof Promise) {if (x.status === PENDING) {
      x.then(
        y => {resolvePromise(promise2, y, resolve, reject)
        },
        reason => {reject(reason)
        }
      )
    } else {x.then(resolve, reject)
    }
  } else if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      // A+ 规定,声明 then = x 的 then 方法
      let then = x.then;
      // 如果 then 是函数,就默认是 promise 了
      if (typeof then === 'function') { 
        // 就让 then 执行 第一个参数是 this   后面是成功的回调 和 失败的回调
        then.call(x, y => {
          // 成功和失败只能调用一个
          if (called) return;
          called = true;
          // resolve 的结果依旧是 promise 那就继续解析
          resolvePromise(promise2, y, resolve, reject);
        }, err => {
          // 成功和失败只能调用一个
          if (called) return;
          called = true;
          reject(err);// 失败了就失败了
        })
      } else { 
           // 返回非 promise,内层 promise 完成,执行 promise2 的 resolve 回归到外城 then 的调用
        resolve(x); // 直接成功即可
      }
    } catch (e) {if (called) return;
      called = true;
      reject(e); 
    }
  } else {resolve(x);
  }
}
  • 思考 1: x === promise2,看下例子
let p1 = new Promise(resolve => {resolve(1);
});
var p2 = p1.then(data => {return p2;})

如果不对 x === promise2 处理,会导致循环引用,自己等待自己

  • 思考 2: else if (x != null && (typeof x === ‘object’ || typeof x === ‘function’))

兼容符合 Promise/A+ 的 then 方法, 使实现方式不同(只要符合规范)的 promise 能够相互调用。

第五版 内外兼修 处理异常

  • then 中 onFulfilled, onRejected 均为非必填参数
  • onFulfilled, onRejected 均为异步调用
  • onFulfilled, onRejected 执行时用 try catch 捕获异常然后 reject(err)
Promise.prototype.then =  function(onFulfilled, onRejected) {

    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    
    // onRejected 若未传,则通过 throw err 的方式将 err 抛出
    onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err};

    let promise2 = new Promise((resolve, reject) => {if (this.state === FULFILLED) {setTimeout(() => {
          try {let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {reject(e);
          }
        }, 0) 
      };
      
      if (this.state === REJECTED) {setTimeout(() => {
          // 如果报错
          try {let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {reject(e);
          }
        }, 0);
      };
      
      if (this.state === PENDING) {this.onFulfilledCallBack.push(()=>{setTimeout(() => {
              try {let x = onFulfilled(this.value);
                resolvePromise(promise2, x, resolve, reject);
              } catch (e) {reject(e);
              }
            }, 0);
        })
        this.onRejectedCallBack.push(()=>{setTimeout(() => {
              try {let x = onRejected(this.reason);
                resolvePromise(promise2, x, resolve, reject);
              } catch (e) {reject(e);
              }
            }, 0);
        })
      }
    })
    
    return promise2
}
  • 到此你已完成了一个 promise

完整代码


// state 3 种状态
const PENDING = 'pending' // 待定
const FULFILLED = 'fulfilled' // 成功
const REJECTED = 'rejected' // 失败

function Promise(executor) {
  this.state = PENDING
  this.value = undefined
  this.reason = undefined
  this.onFulfilledCallBack = []
  this.onRejectedCallBack = []
  let resolve = value => {if (this.state === 'pending') {
      this.state = 'fulfilled'
      this.value = value
      this.onFulfilledCallBack.forEach(fn => fn())
    }
  }
  let reject = reason => {if (this.state === 'pending') {
      this.state = 'rejected'
      this.reason = reason
      this.onRejectedCallBack.forEach(fn => fn())
    }
  }
  try {executor(resolve, reject)
  } catch (err) {reject(err)
  }
}

Promise.prototype.then = function(onFulfilled, onRejected) {
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value

  // onRejected 若未传,则通过 throw err 的方式将 err 抛出
  onRejected =
    typeof onRejected === 'function'
      ? onRejected
      : err => {throw err}

  let promise2 = new Promise((resolve, reject) => {if (this.state === FULFILLED) {setTimeout(() => {
        try {let x = onFulfilled(this.value)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {reject(e)
        }
      }, 0)
    }
    if (this.state === REJECTED) {setTimeout(() => {
        // 如果报错
        try {let x = onRejected(this.reason)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {reject(e)
        }
      }, 0)
    }
    if (this.state === PENDING) {this.onFulfilledCallBack.push(() => {setTimeout(() => {
          try {let x = onFulfilled(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {reject(e)
          }
        }, 0)
      })
      this.onRejectedCallBack.push(() => {setTimeout(() => {
          try {let x = onRejected(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {reject(e)
          }
        }, 0)
      })
    }
  })

  return promise2
}

function resolvePromise(promise2, x, resolve, reject) {
  // 处理循环引用
  if (x === promise2) {
    // reject 报错
    return reject(new TypeError('Chaining cycle detected for promise'))
  }
  // 防止多次调用
  let called
  if (x instanceof Promise) {if (x.status === PENDING) {
      x.then(
        y => {resolvePromise(promise2, y, resolve, reject)
        },
        reason => {reject(reason)
        }
      )
    } else {x.then(resolve, reject)
    }
  } else if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      // A+ 规定,声明 then = x 的 then 方法
      let then = x.then
      // 如果 then 是函数,就默认是 promise 了
      if (typeof then === 'function') {
        // 就让 then 执行 第一个参数是 this   后面是成功的回调 和 失败的回调
        then.call(
          x,
          y => {
            // 成功和失败只能调用一个
            if (called) return
            called = true
            // resolve 的结果依旧是 promise 那就继续解析
            resolvePromise(promise2, y, resolve, reject)
          },
          err => {
            // 成功和失败只能调用一个
            if (called) return
            called = true
            reject(err) // 失败了就失败了
          }
        )
      } else {
        // 返回非 promise,内层 promise 完成,执行 promise2 的 resolve 回归到外城 then 的调用
        resolve(x) // 直接成功即可
      }
    } catch (e) {if (called) return
      called = true
      reject(e)
    }
  } else {resolve(x)
  }
}
退出移动版