小言_互联网的博客

前端学习笔记——promise实现

488人阅读  评论(0)

这个前端学习笔记是学习gitchat上的一个课程,这个课程的质量非常好,价格也不贵,非常时候前端入门的小伙伴们进阶。

笔记不会涉及很多,主要是提取一些知识点,详细的大家最好去过一遍教程,相信你一定会有很大的收获

使用

我们看下微信小程序的请求API

wx.request({
  url: 'test.php', // 仅为示例,并非真实的接口地址
  data: {
    x: '',
    y: ''
  },
  header: {
    'content-type': 'application/json' // 默认值
  },
  success(res) {
    console.log(res.data)
  }
})

如果需要通过请求结果再次请求,就会出现回调地狱

wx.request({
  url: 'test.php', // 仅为示例,并非真实的接口地址
	//...
  success(res) {
    wx.request({
      url: 'test.php', // 仅为示例,并非真实的接口地址
        //...
      success(res) {
        console.log(res.data)
      }
    })
  }
})

我们现在使用promise来改善这种写法

const require = (url, data, method)=>{
    return new Promise((res,rej)=>{
        wx.request({
            url,
            data,
            method,
            head:{},
            success:(data)=>{
                res(data)
            },
            fail:(error)=>{
                rej(error)
            }
        })
    }})
}
// 多层请求嵌套
require('test.php', {data:'1'}, 'get').then((data)=>{
    return require('test.php', data, 'get')
}).then((data)=>{
    return require('test.php', data, 'get')
})// 可以继续嵌套

从上面可知,promise 是一个构造函数,使用的是promise的实例。

接受一个函数参数,而这个函数的参数有2个,分别代表这成功回调和失败回调

then接受的2个函数参数分别对应着上面的2个函数参数

function Promise(fn) {
	// fn(onfulfilled, onrejected){}
}

Promise.prototype.then = function(onfulfilled, onrejected) {

}

在promise构造函数的时候,通过onfulfilled, onrejected将任务时间放置到异步队列,只要有一个触发,另外一个就没有用了,也是promise的唯一性,所以需要内部有2个值保存着这个函数。

promise还设置了一个状态(pending,fulfilled,rejected)

function Promise(fn) {
  const self = this
  this.status = 'pending'
  this.value = null// 成功回调的值
  this.reason = null// 失败回调的原因

  function resolve(value) {
    self.value = value
  }

  function reject(reason) {
    self.reason = reason
  }

  fn(resolve, reject)// 将then设置的函数回调传给fn
}
// 为了保证 onfulfilled、onrejected 能够强健执行,我们为其设置了默认值,其默认值为一个函数元(Function.prototype)。
Promise.prototype.then = function(onfulfilled = Function.prototype, onrejected = Function.prototype) {
  onfulfilled(this.value)

  onrejected(this.reason)
}

状态完善

从promise题目来看看状态的作用

let promise = new Promise((resolve, reject) => {
  resolve('data')
  reject('error')
})

promise.then(data => {
  console.log(data)
}, error => {
  console.log(error)
})

只会输出data,因为只能执行一个回调,虽然函数里调用了2个回调参数,但是promise只会执行第一个,这是因为promise有一个状态机制。

所以我们需要在内部加一个状态,来控制回调的执行。

function Promise(fn) {
  this.status = 'pending'// 初始状态
  this.value = null
  this.reason = null

  const resolve = value => {
    if (this.status === 'pending') {// 如果用户执行了 resolve 则改变状态为fulfilled
      this.value = value
      this.status = 'fulfilled'
    }
  }

  const reject = reason => {
    if (this.status === 'pending') {// 如果用户执行了 reject 则改变状态为rejected
      this.reason = reason
      this.status = 'rejected'
    }
  }

  fn(resolve, reject)
}
// 参数加多层判断,如果传入不是函数的话,将它转换成函数
Promise.prototype.then = function(onfulfilled, onrejected) {
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
  onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}

  if (this.status === 'fulfilled') {// 避免在加载的时候就执行成功回调
    onfulfilled(this.value)
  }
  if (this.status === 'rejected') {// 避免在加载的时候就执行失败回调
    onrejected(this.reason)
  }
}

异步执行

从例子看看promise如何进行异步处理

let promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('data')
  }, 2000)
})

promise.then(data => {
  console.log(data)
})
// 2秒之后输出data

由于我们2秒之后才调用resolve,所以promise2秒内状态都没有发生变化。虽然使用then,但是状态一直是pending,所以无法执行onfulfilled,所以我们需要在内部中在promise改变状态的时候,自己调用onfulfilled

function Promise(fn) {
  this.status = 'pending'
  this.value = null
  this.reason = null
  this.onFulfilledFunc = Function.prototype
  this.onRejectedFunc = Function.prototype

  const resolve = value => {
    if (this.status === 'pending') {
      this.value = value
      this.status = 'fulfilled'
		// 等待执行
      this.onFulfilledFunc(this.value)
    }

  }

  const reject = reason => {
    if (this.status === 'pending') {
      this.reason = reason
      this.status = 'rejected'
		// 等待执行
      this.onRejectedFunc(this.reason)
    }
  }

  fn(resolve, reject)
}

Promise.prototype.then = function(onfulfilled, onrejected) {
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
  onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}

  if (this.status === 'fulfilled') {
    onfulfilled(this.value)
  }
  if (this.status === 'rejected') {
    onrejected(this.reason)
  }
  if (this.status === 'pending') {
      // 保存回调函数
    this.onFulfilledFunc = onfulfilled
    this.onRejectedFunc = onrejected
  }
}

但是如果不是异步调用resolve的话,就会导致执行顺序和原本promise的不一样

let promise = new Promise((resolve, reject) => {
   resolve('data')
})

promise.then(data => {
  console.log(data)
})
console.log(1)

正常会输出 1然后输出 data,但是我们实现的没有考虑这个情况,输出是data然后是1

所以我们在promise内部同样需要进行一个异步处理。

我们要知道,promise异步是微任务的,可以使用nodejs 的nextTick或则mutationObserve来模拟,我们这里使用setTimeout来实现。

const  resolve = value => {
  if (value instanceof Promise) {// 传入的如果是promise 链模式
    return value.then(resolve, reject)
  }
  setTimeout(() => {
    if (this.status === 'pending') {
      this.value = value
      this.status = 'fulfilled'

      this.onFulfilledFunc(this.value)
    }
  })
}

const reject = reason => {
  setTimeout(() => {
    if (this.status === 'pending') {
      this.reason = reason
      this.status = 'rejected'

      this.onRejectedFunc(this.reason)
    }
  })
}

看一下到目前为止的实现代码:

function Promise(executor) {
  this.status = 'pending'
  this.value = null
  this.reason = null
  this.onFulfilledFunc = Function.prototype
  this.onRejectedFunc = Function.prototype

  const resolve = value => {
    if (value instanceof Promise) {
      return value.then(resolve, reject)
    }
    setTimeout(() => {
      if (this.status === 'pending') {
        this.value = value
        this.status = 'fulfilled'

        this.onFulfilledFunc(this.value)
      }
    })
  }

  const reject = reason => {
    setTimeout(() => {
      if (this.status === 'pending') {
        this.reason = reason
        this.status = 'rejected'

        this.onRejectedFunc(this.reason)
      }
    })
  }

  executor(resolve, reject)
}

Promise.prototype.then = function(onfulfilled, onrejected) {
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
  onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}

  if (this.status === 'fulfilled') {
    onfulfilled(this.value)
  }
  if (this.status === 'rejected') {
    onrejected(this.reason)
  }
  if (this.status === 'pending') {
    this.onFulfilledFunc = onfulfilled
    this.onRejectedFunc = onrejected
  }
}

这样,promise的输出就能够异步进行了。

细节完善

因为promise是支持链式添加then,所以我们需要返回一个promise作为结果。

let promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('data')
  }, 2000)
})

promise.then(data => {
  console.log(`1: ${data}`)
})
promise.then(data => {
  console.log(`2: ${data}`)
})

输出情况

//1: data
//2: data

这就需要将内部保存的onFulfilledFunc变成一个栈保存,使用数组即可。

function Promise(fn) {
  this.status = 'pending'
  this.value = null
  this.reason = null
  this.onFulfilledArray = []
  this.onRejectedArray = []

  const resolve = value => {
    if (value instanceof Promise) {
      return value.then(resolve, reject)
    }
    setTimeout(() => {
      if (this.status === 'pending') {
        this.value = value
        this.status = 'fulfilled'

        this.onFulfilledArray.forEach(func => {// 按顺序调用
          func(value)
        })
      }
    })
  }

  const reject = reason => {
    setTimeout(() => {
      if (this.status === 'pending') {
        this.reason = reason
        this.status = 'rejected'

        this.onRejectedArray.forEach(func => {// 按顺序调用
          func(reason)
        })
      }
    })
  }

  fn(resolve, reject)
}

Promise.prototype.then = function(onfulfilled, onrejected) {
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
  onrejected = typeof onrejected === 'function' ? onrejected : error => {throw error}

  if (this.status === 'fulfilled') {
    onfulfilled(this.value)
  }
  if (this.status === 'rejected') {
    onrejected(this.reason)
  }
  if (this.status === 'pending') {// 栈保存
    this.onFulfilledArray.push(onfulfilled)
    this.onRejectedArray.push(onrejected)
  }
}

还有一个细节,如果传入的函数有语法错误,promise的状态会变成rejected

我们只需要使用try-catch来执行函数,如果出错就调用rejected

try {
  fn(resolve, reject)
} catch(e) {
  reject(e)
}

最终版本:

function Promise(executor) {
  this.status = 'pending'
  this.value = null
  this.reason = null
  this.onFulfilledArray = []
  this.onRejectedArray = []

  const resolve = value => {
    if (value instanceof Promise) {
      return value.then(resolve, reject)
    }
    setTimeout(() => {
      if (this.status === 'pending') {
        this.value = value
        this.status = 'fulfilled'

        this.onFulfilledArray.forEach(func => {
          func(value)
        })
      }
    })
  }

  const reject = reason => {
    setTimeout(() => {
      if (this.status === 'pending') {
        this.reason = reason
        this.status = 'rejected'

        this.onRejectedArray.forEach(func => {
          func(reason)
        })
      }
    })
  }


  try {
    executor(resolve, reject)
  } catch(e) {
    reject(e)
  }
}

Promise.prototype.then = function(onfulfilled, onrejected) {
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
  onrejected = typeof onrejected === 'function' ? onrejected : error => { throw error}

  if (this.status === 'fulfilled') {
    onfulfilled(this.value)
  }
  if (this.status === 'rejected') {
    onrejected(this.reason)
  }
  if (this.status === 'pending') {
    this.onFulfilledArray.push(onfulfilled)
    this.onRejectedArray.push(onrejected)
  }
}function Promise(executor) {
  this.status = 'pending'
  this.value = null
  this.reason = null
  this.onFulfilledArray = []
  this.onRejectedArray = []

  const resolve = value => {
    if (value instanceof Promise) {
      return value.then(resolve, reject)
    }
    setTimeout(() => {
      if (this.status === 'pending') {
        this.value = value
        this.status = 'fulfilled'

        this.onFulfilledArray.forEach(func => {
          func(value)
        })
      }
    })
  }

  const reject = reason => {
    setTimeout(() => {
      if (this.status === 'pending') {
        this.reason = reason
        this.status = 'rejected'

        this.onRejectedArray.forEach(func => {
          func(reason)
        })
      }
    })
  }


  try {
    executor(resolve, reject)
  } catch(e) {
    reject(e)
  }
}

Promise.prototype.then = function(onfulfilled, onrejected) {
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
  onrejected = typeof onrejected === 'function' ? onrejected : error => { throw error}

  if (this.status === 'fulfilled') {
    onfulfilled(this.value)
  }
  if (this.status === 'rejected') {
    onrejected(this.reason)
  }
  if (this.status === 'pending') {
    this.onFulfilledArray.push(onfulfilled)
    this.onRejectedArray.push(onrejected)
  }
}

链式调用

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('rex')
  }, 2000)
})

promise.then(data => {
  console.log(data)
  return `${data} next then`
})
.then(data => {
  console.log(data)
})

这段代码执行后,将会在 2 秒后输出:rex,紧接着输出:rex next then。

初步实现

我们分解上一个例子,第一次绑定的then是

data => {
  console.log(data)
  return `${data} next then`
}

我们可以在then方法里 添加一个返回新的promise

Promise.prototype.then = function(onfulfilled, onrejected) {
  // promise2 将作为 then 方法的返回值
  let promise2
  if (this.status === 'fulfilled') {
    return promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                try {
                    // 这个新的 promise2 resolved 的值为 onfulfilled 的执行结果
                    let result = onfulfilled(this.value)
                    resolve(result)
                }
                catch(e) {
                    reject(e)
                }
            })
    })
  }
  if (this.status === 'rejected') {
    return promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                try {
                    // 这个新的 promise2 reject 的值为 onrejected 的执行结果
                    let result = onrejected(this.value)
                    resolve(result)
                }
                catch(e) {
                    reject(e)
                }
            })
    })
  }
  if (this.status === 'pending') {
    return promise2 = new Promise((resolve, reject) => {
      this.onFulfilledArray.push(() => {
        try {
          let result = onfulfilled(this.value)
          resolve(result)
        }
        catch(e) {
          reject(e)
        }
      })

      this.onRejectedArray.push(() => {
        try {
          let result = onrejected(this.reason)
          resolve(result)
        }
        catch(e) {
          reject(e)
        }
      })      
    })
  }
}

前面2中状态都好理解,pendding这个状态是如何让promise能够执行后续的then?

返回的新的promise2其实将promise2then执行函数保存在promise1的异步任务里去了,当promise1执行结束的时候,promise2then开始执行,添加到异步任务去,这样就能够执行第二个then,同理promise2调用then会返回第三个的promise

继续完善

如果用户显式返回promise

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
      resolve('rex')
  }, 2000)
})

promise.then(data => {
  console.log(data)
  return new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(`${data} next then`)
    }, 4000)
  })
})
.then(data => {
  console.log(data)
})

我们需要将result返回的结果进行判断,一种是普通值,一种是promise的实例

为此我们抽象出 resolvePromise 方法进行统一处理。改动已有实现为

const resolvePromise = (promise2, result, resolve, reject) => {

}

这个函数接受四个参数:

  • promise2: 返回的 Promise 实例
  • result: onfulfilled 或者 onrejected 函数的返回值
  • resolve: promise2 的 resolve 方法
  • reject: promise2 的 reject 方法
const resolvePromise = (promise2, result, resolve, reject) => {
  // 当 result 和 promise2 相等时,也就是说 onfulfilled 返回 promise2 时,进行 reject
    // 避免死循环
  if (result === promise2) {
    reject(new TypeError('error due to circular reference'))
  }

  // 是否已经执行过 onfulfilled 或者 onrejected
  let consumed = false
  let thenable

  if (result instanceof Promise) {
    if (result.status === 'pending') {
      result.then(function(data) {
        resolvePromise(promise2, data, resolve, reject)
      }, reject)
    } else {
      result.then(resolve, reject)
    }
    return
  }

  let isComplexResult = target => (typeof target === 'function' || typeof target === 'object') && (target !== null)

  // 如果返回的是疑似 Promise 类型
  if (isComplexResult(result)) {
    try {
      thenable = result.then
      // 如果返回的是 Promise 类型,具有 then 方法
      if (typeof thenable === 'function') {
        thenable.call(result, function(data) {
          if (consumed) {
            return
          }
          consumed = true

          return resolvePromise(promise2, data, resolve, reject)
        }, function(error) {
          if (consumed) {
            return
          }
          consumed = true

          return reject(error)
        })
      }
      else {
        resolve(result)
      }

    } catch(e) {
      if (consumed) {
        return
      }
      consumed = true
      return reject(e)
    }
  }
  else {
    resolve(result)
  }
}

对于onfulfilled函数返回的结果result:如果result 非 Promise 实例,非对象,非函数类型,是一个普通值的话(上述代码中isComplexResult 函数进行判断),我们直接将 promise2 以该值 resolve 掉。

对于onfulfilled 函数返回的结果result:如果result 含有then属性方法,我们称该属性方法为 thenable,说明result 是一个 Promise 实例,我们执行该实例的then 方法(既thenable),此时的返回结果有可能又是一个 Promise 实例类型,也可能是一个普通值,因此还要递归调用resolvePromise

从例子看为什么要递归调用

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
      resolve('rex')
  }, 2000)
})

promise.then(data => {
  console.log(data)
  return new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(`${data} next then`)
    }, 4000)
  })
  .then(data => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
          resolve(`${data} next then`)
      }, 4000)
    })
  })
})
.then(data => {
  console.log(data)
})

该段代码将会在 2 秒是输出:rex,10 秒时输出:rex next then next then。

promise 穿透

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
      resolve('rex')
  }, 2000)
})


promise.then(null)
.then(data => {
  console.log(data)
})

2秒后输出rex,这就是 Promise 穿透现象:

给 .then() 函数传递非函数值作为其参数时,实际上会被解析成 .then(null),这时候的表现应该是:上一个 promise 对象的结果进行“穿透”,如果在后面链式调用仍存在第二个 .then() 函数时,将会获取被穿透下来的结果。

其实很简单,并且我们已经做到了。想想在 then() 方法的实现中:我们已经对 onfulfilled 和 onrejected 函数加上判断:

Promise.prototype.then = function(onfulfilled = Function.prototype, onrejected = Function.prototype) {
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
  onrejected = typeof onrejected === 'function' ? onrejected : error => { throw error }

    // ...
}

如果 onfulfilled 不是函数类型,则给一个默认值,该默认值是返回其参数的函数。onrejected 函数同理。这段逻辑,就是起到了实现“穿透”的作用。

promise静态方法

  • Promise.prototype.catch
  • Promise.resolve,Promise.reject
  • Promise.all
  • Promise.race

Promise.prototype.catch 实现

Promise.prototype.catch 可以进行异常捕获,它的典型用法:

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
      reject('rex error')
  }, 2000)
})

promise1.then(data => {
  console.log(data)
}).catch(error => {
  console.log(error)
})

会在 2 秒后输出:rex error。

实现:

Promise.prototype.catch = function(catchFunc) {
  return this.then(null, catchFunc)
}

Promise.prototype.resolve实现

Promise.resolve(value) 方法返回一个以给定值解析后的 Promise 实例对象。

Promise.resolve('data').then(data => {
  console.log(data)
})
console.log(1)

先输出 1 再输出 data。

实现

Promise.resolve = function(value) {
  return new Promise((resolve, reject) => {
    resolve(value)
  })
}

Promise.all 实现

Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果。

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
      resolve('rex')
  }, 2000)
})

const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
      resolve('rex')
  }, 2000)
})

Promise.all([promise1, promise2]).then(data => {
  console.log(data)
})

将在 2 秒后输出:[“rex”, “rex”]。

Promise.all = function(promiseArray) {
  if (!Array.isArray(promiseArray)) {
      throw new TypeError('The arguments should be an array!')
  }
  return new Promise((resolve, reject) => {
    try {
      let resultArray = []

      const length = promiseArray.length

      for (let i = 0; i <length; i++) {
        promiseArray[i].then(data => {
          resultArray.push(data)// 结果保存到数组
			// 全部异步执行完毕,返回最终结果
          if (resultArray.length === length) {
            resolve(resultArray)
          }
        }, reject)
      }
    }
    catch(e) {
      reject(e)
    }
  })
}

这里用闭包保存一个length长度,没成功执行一个promise就加1,这里没有加1,但是是使用result结果长度来判断,如果长度一样则代表所有异步已经全部返回,知道所有异步执行完成,就执行promise.all的异步任务,把所有结果当作resolve的参数

Promise.race 实现

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
      resolve('lucas1')
  }, 2000)
})

const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
      resolve('lucas2')
  }, 4000)
})

Promise.race([promise1, promise2]).then(data => {
  console.log(data)
})

将会在 2 秒后输出:lucas1,实现 Promise.race 为:

Promise.race = function(promiseArray) {
  if (!Array.isArray(promiseArray)) {
      throw new TypeError('The arguments should be an array!')
  }
  return new Promise((resolve, reject) => {
    try {
          const length = promiseArray.length
      for (let i = 0; i <length; i++) {
        promiseArray[i].then(resolve, reject)// 第一个执行成功的会调用resolve
      }
    }
    catch(e) {
      reject(e)
    }
  })
}

这里使用 for 循环同步执行 promiseArray 数组中的所有 promise 实例 then 方法,第一个 resolve 的实例直接会触发新 Promise(代码中新 new 出来的) 实例的 resolve 方法。后续调用resolve就会失效了。


转载:https://blog.csdn.net/gdutRex/article/details/102466891
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场