【手写】promise

大纲
前置知识
手写promise

  • resolve
  • reject
  • then 不传参的处理
  • 保证执行顺序,异步实现 then方法的代码在同步代码之后执行或者状态改变之后执行Promise参数函数中有异步操作时
  • then的链式调用
  • all
  • catch
  • race

前置知识

1. promise基础知识
- promise是一个对象,可以通过new Promise()的方式生成。
- promise是一个容器,存放着未来才会结束的事件,通常是一个异步操作的结果


2. promise的特点
- 对象的状态不受外界影响 ( pending, fulfilled, rejected )
  - 只有异步操作的结果可以决定当前是那一种状态,其他操作都不能改变这个状态
- 状态一旦改变,就不会再变,任何时候都能得到这个结果
  - promise状态改变只有两种可能,从pending变为fulfilled,从pending变为rejected


3. promise的缺点
- 无法取消promise,一旦新建就会立即执行,无法中途取消
- 如果不设置回调函数,promise内部抛出的错误不会反应到外部
- 处于pending状态时,无法得知目前进展到那一个阶段(刚开始还是即将完成)


4. resolve()
- 将promise对象的状态由:pending变为fulfilled
- 在异步操作(成功)时调用,并将异步操作的(结果)作为(参数)传递出去
- 参数可以是(正常的值)或者是一个(promise实例)
- 当resolve()函数的参数是一个promise实例对象时:
const p1 = new Promise( (resolve, reject) => {
  setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => resolve(p1), 1000)
})
p2.then(result => console.log(result)).catch(error => console.log(error))
解析:
1. p2中resolve的是一个promise实例对象p1,导致p2的状态由p1决定
2. 1s时 p2状态在改变,但是此时状态改变无效,应为resolve的是一个promise实例,p2状态由p1的状态决定
3. 再过2s时,p1状态变为reject,会导致p2状态变为reject,被catch捕获



5. reject()
- 将promise对象的状态由:pending变为rejected
- 在异步操作失败时调用,并将异步操作爆出的错误,通过参数传递出去
- 参数通常是(Error对象的实例)


6. 注意:resolve和reject函数并不会终止promise参数函数的执行,所以一般都会加上return防止后面的语句继续执行


7. then() ------------------- 返回值是一个新的promie实例对象,后面可以接着调用then方法 ----------
- promise实例对象生成后,可以then方法分别指定resolve状态和reject状态的回调函数
- then方法有两个参数,第一个在状态变为fulfilled时调用,第二个在状态变为rejected时调用,第二个参数可选
- then()方法的两个参数函数的 (参数 )是promise对象传出的值
- then()定义在原型上,作用是添加状态改变时的回调函数
- (then)方法返回的是:(新的promise实例对象),不是原来的promise实例
- 注意:
  - 1. then方法的参数是状态改变后的回调函数,回调函数的参数是promise状态改变时抛出的值
  - 2. 但是then方法有返回值,返回的是一个新的promise实例,可以链式调用then,此时后面的then的参数是前面then的返回值
  - 3. 如果前面then返回的是promise对象,则后面的then的回调函数将在前面then状态改变后执行


8. catch ------------------- 返回值是一个新的promie实例对象,后面可以接着调用then方法 ----------
- 指定发生错误的回调函数
- 是 .then(null, rejection)或.then(undefined, rejection)的别名
- 能捕获promise中抛出的错误,也能捕获then方法中抛出的错误
- catch返回的是一个promise实例


9. finally
- 指定promise对象最后的状态无论为何,都会执行的操作
- finally指定的回调函数,不接受任何参数,表明finally函数里面的操作与promise状态无关,不依赖promise执行的结果
- 注意:
  - finally返回的是一个promise实例对象,并且是前面的值


10. all
- 用于将多个promise实例,包装成一个新的promise实例
- 参数是一个数组(如果不是数组,必须是具有Iterator接口的数据类型),成员是promise实例
- 如果参数数组的成员不是promise实例,会先调用Promise.resolve()将其转换成promise后在进一步处理
- 
const p = Promise.all([p1, p2, p3]);
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
- 
注意:
1. 如果promise实例自己定义了catch方法,该promise实例中抛出的错误不会被promise.all的catch捕获
2. 因为promise自己的catch方法返回的是一个新的promise实例,catch中没有抛错的话,到promise.all()时的参数数组的成员是这个新的promise实例
3. 所以Promise.all()参数数组中的都是resolove状态
4. 如果p2没有自己的catch方法,就会调用Promise.all()的catch方法
const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result => result)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
})
.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]


11. race
- 将多个promise实例包装成新的promise实例
- 
const p = Promise.race([p1, p2, p3]);
1. 只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。
2. 如果参数数组成员不是一个promise,则会先调用Promise.all()转成promise实例






12. allSettled ------------------- 返回值是一个新的promie实例对象
- settled:结束
- 将多个promise实例包装成新的promise实例
- 参数是一个数组,成员是promise实例
- 在所有promise实例状态都改变后(不管是fulfilled还是rejected),包装实例才会结束
- 该方法返回的新的 Promise 实例,一旦结束,状态总是fulfilled,不会变成rejected。
const promises = [
  fetch('/api-1'),
  fetch('/api-2'),
  fetch('/api-3'),
];
await Promise.allSettled(promises);
removeLoadingIndicator();
上面代码对服务器发出三个请求,等到三个请求都结束,不管请求成功还是失败,加载的滚动图标就会消


----------

const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
  console.log(results);
});
// [
//    { status: 'fulfilled', value: 42 },
//    { status: 'rejected', reason: -1 }
// ]
解析:
1. Promise.allSettled返回的是新的promise实例,状态只可是 fulfilled 
2. 状态改变成fulfilled的监听函数(即then函数)的参数是一个数组,成员都是对象
3. 成员对象中都具有status属性,值是 'fulfilled'或者'rejected'
4. 成员对象中,当是 fulfilled时,具有value属性
5. 成员对象中,当是 rejected时,具有reason属性
6. 使用场景:
 - 有时候,我们不关心异步操作的结果,只关心这些操作有没有结束。这时,Promise.allSettled()方法就很有用。
 - Promise.all()不能保证所有操作都结束后在执行某些操作,因为Promise.all()的rejected状态是谁先rejected,则状态就变为rejected





13. any  // ----- (和all相反) (和all相反) (和all相反)
- Promise.any()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。
- 只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态
- 所有参数实例都变成rejected状态,包装实例就会变成rejected状态。
- Promise.any()抛出的错误,不是一个一般的错误,而是一个 AggregateError 实例。
  它相当于一个数组,每个成员对应一个被rejected的操作所抛出的错误。




14. resolve
- 参数
1. 参数是一个 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
2. 参数是一个thenable对象(指的是具有then方法的对象)
  - 会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法
3. 参数不是具有then方法的对象,或根本就不是对象
  - Promise.resolve方法返回一个新的 Promise 对象,状态为resolved
4. 不带有任何参数
  - Promise.resolve()方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。


15. try
- Promise.try
- //
const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next

手写promise

class MyPromise {
    constructor(executor) {
    
        if(typeof executor !== 'function') {
            throw new TypeError(` Promise resolver ${executor} is not a function`)
        }

        this.init()
        
        // 这里用try...catch是因为
        // 在promise的参数函数中的错误需要被then中的第二个回调函数所捕获,或者被catch函数所捕获
        // 如果在promise的参数参数中抛出错误,就用catch捕获,用this.reject函数去改变状态和存储拒因
        try {
            executor(this.resolve, this.reject)
        } catch(err) {
            this.reject(err)
        }
    }
    init = () => {
        this.status = MyPromise.PENDING // 状态
        this.value = null // 终值
        this.reason = null // 拒因
        this.onFulfilledCallbacks = [] // 成功回调数组
        this.onRejectedCallbacks = [] // 失败回调数组
    }
    resolve = (value) => {
        // 状态的改变,成功回调的执行
        if(this.status === MyPromise.PENDING) {
            this.status = MyPromise.FULFILLED
            this.value = value
            this.onFulfilledCallbacks.forEach(fn => fn(this.value)) // 用于异步的情况
        }
    }
    reject = (reason) => {
        // 状态的改变,失败回调的执行
        if(this.status === MyPromise.PENDING) {
            this.status = MyPromise.REJECTED
            this.reason = reason
            this.onRejectedCallbacks.forEach(fn => fn(this.reason))
        }
    }
    then = (onFulfilled, onRejected) => {
        // 两个参数都是可选的
        // 如果不是函数则被忽略
        if(typeof onFulfilled !== MyPromise.Function) {
            // 如果第一个参数不是函数,就把第一个参数设置成函数,该函数返回成功时的终值
            // 并且该函数要在状态变为fulfilled时执行,参数就是终值
            onFulfilled = value => value
        }
        if(typeof onRejected !== MyPromise.Function) {
            // 如果第二个参数不是函数,就把第二个参数设置成函数,该函数返回失败时候的拒因
            // 并且该函数要在状态变为fulfilled时执行,参数就是终值
            onRejected = reason => { 
                throw reason 
            }
        }  
        
        

        // 注意:
        // then方法要实现链式调用,必须返回一个新的实例,如果是原来的promise实例
        // then方法返回一个新的promise实例,即promise2
        let promise2 = new Promise((resolve2, reject2) => {
            
            if(this.status === MyPromise.FULFILLED) {
                // 这里用setTimeout的原因是:执行顺序
                // 如果不用定时器,then方法会立即执行,则不是微任务的表现
                setTimeout(() => {
                    try {
                        const x = onFulfilled(this.value)
                        MyPromise.resolvePromise(promise2, x, resolve2, reject2)
                    } catch(err2) {
                        reject2(err2)
                    }
                }, 0)
            }
            if(this.status === MyPromise.REJECTED) {
                setTimeout(() => {
                    try {
                        const x = onRejected(this.reason)
                        MyPromise.resolvePromise(promise2, x, resolve2, reject2)
                    } catch(err2) {
                        reject2(err2)
                    }
                }, 0)
            }
            if(this.status === MyPromise.PENDING) {
                // 当promise的参数函数中有异步操作时,then方法会优先于resolve()或者reject()先执行
                // 这样就是导致执行then()方法时,状态是pending状态
                // 这是需要用一个数组来存储将来才会执行的onFulfilled函数
                // 这里push进onFulfilledCallbacks的函数,将在resolve()函数中去执行
                this.onFulfilledCallbacks.push(value => {
                    setTimeout(() => {
                        // 这里之所以还要用setTimeout是因为在成功的回调函数中,resolve()后面还有同步代码的话,要保证把同步执行完,在去执行resolve函数
                        // 如下:
                        // console.log('4')
                        // resolve('5')
                        // console.log('6')
                        // 要保证 4 6 5这样的顺序
                        try {
                            const x = onFulfilled(value)
                            MyPromise.resolvePromise(promise2, x, resolve2, reject2)
                        } catch(err2) {
                            reject2(err2)
                        }
                    })
                })
                this.onRejectedCallbacks.push(reason => {
                    
                    setTimeout(() => {
                        try {
                            const x = onRejected(reason)
                            MyPromise.resolvePromise(promise2, x, resolve2, reject2)
                        } catch(err2) {
                            reject2(err2)
                        }
                    })
                })
            }

        })
        return promise2

    }
}
MyPromise.PENDING = 'PENDING'
MyPromise.FULFILLED = 'FULFILLED'
MyPromise.REJECTED = 'REJECTED'
MyPromise.Function = 'function'
MyPromise.resolvePromise = function(promise2, x, resolve, reject) {
    // x 与 promise 相等
    if (promise2 === x) {
      reject(new TypeError('Chaining cycle detected for promise'))
    }
  
    let called = false
    if (x instanceof MyPromise) {
      // 判断 x 为 Promise
      x.then(
        value => {
            MyPromise.resolvePromise(promise2, value, resolve, reject)
        },
        reason => {
          reject(reason)
        }
      )
    } else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
      // x 为对象或函数
      try {
        const then = x.then
        if (typeof then === 'function') {
          then.call(
            x,
            value => {
              if (called) return
              called = true
              MyPromise.resolvePromise(promise2, value, resolve, reject)
            },
            reason => {
              if (called) return
              called = true
              reject(reason)
            }
          )
        } else {
          if (called) return
          called = true
          resolve(x)
        }
      } catch (e) {
        if (called) return
        called = true
        reject(e)
      }
    } else {
      resolve(x)
    }
}

Promise.defer = Promise.deferred = function() {
    let dfd = {}
    dfd.promise = new Promise((resolve, reject) => {
      dfd.resolve = resolve
      dfd.reject = reject
    })
    return dfd
}

module.exports = MyPromise

https://github.com/dream2023/blog/tree/master/promise

then方法没有传参时

  • 需要重写 onFulfilled函数,返回当前的终值 this.value
  • 需要重写 onRejected函数,返回当前的拒因 this.reason,并抛出
 then = (onFulfilled, onRejected) => {
        if (typeof onFulfilled !== this.FUNCTION) {
            // 没传onFulfilled参数,就重写该函数
            // 将调用参数原样返回
            // 这里没有直接写 (typeof onFulfilled !== 'function') 防止魔法字符串
            onFulfilled = value => value 
        }
        if (typeof onRejected !== this.FUNCTION) {
             // 没传onRejected参数,就重写该函数
             // 抛出reason
            onRejected = reason => {
                throw reason
            }
        }
        if (this.status === this.FULFILLED) {
            // 是fulfilled状态是,才执行onFulfilled函数,参数是当前的终值
            // 即状态改变时为成功时,添加的回调函数
            // 这里传参和没有传参都会执行,没传参是执行重写过后的onFulfilled
            onFulfilled(this.value)
        }
        if (this.status === this.REJECTED) {
            onRejected(this.reason)
        }
    }

如何保证执行顺序1

console.log(1)
new Promise((resolve, reject) => {
    console.log(2)
    resolve(1)
}).then(() =>  console.log(4))
console.log(3)



问题:如何保证执行顺序是 1234
解决:说明then方法中的代码要异步执行,用定时器模拟解决
说明:如果不用定时器,执行顺序是1243



 then = (onFulfilled, onRejected) => {
        if (typeof onFulfilled !== this.FUNCTION) {
            // 没传onFulfilled参数,就重写该函数
            // 将调用参数原样返回
            // 这里没有直接写 (typeof onFulfilled !== 'function') 防止魔法字符串
            onFulfilled = value => value 
        }
        if (typeof onRejected !== this.FUNCTION) {
             // 没传onRejected参数,就重写该函数
             // 抛出reason
            onRejected = reason => {
                throw reason
            }
        }
        if (this.status === this.FULFILLED) {
            // 是fulfilled状态是,才执行onFulfilled函数,参数是当前的终值
            // 即状态改变时为成功时,添加的回调函数
            // 这里传参和没有传参都会执行,没传参是执行重写过后的onFulfilled
            
            // 用定时器解决代码的执行顺序
            // 用定时器保证then方法中的参数函数是在同步代码之后执行
            setTimeout(() => onFulfilled(this.value), 0)
        }
        if (this.status === this.REJECTED) {
            setTimeout(() => onRejected(this.reason), 0)
            
        }
    }  

如何保证执行顺序2

console.log(1)
new Promise((resolve, reject) => {
    console.log(2)
    setTimeout(() => resolve())
   // 当这里有异步操作时,上面的代码打印只有 123,注意 4 并未打印
   // 原因是then()方法在resolve()方法前执行了,因为resolve是异步的,导致 then() 中的状态还是 pending 状态
   // 而在then方法中并为添加状态是pending状态时的相关操作
}).then(() =>  console.log(4))
console.log(3)



问题:打印出了123,但是并未打印4
分析:
  1. 原因是then()方法在resolve()方法前执行了,因为resolve是异步的,导致 then() 中的状态还是 pending 状态
  2. 而在then方法中并为添加状态是pending状态时的相关操作
解决:
  1. 在then()方法中添加pending状态下的相关判断
      - 并向 onFulfilledCallbacks 数组中push一个方方法,该方中去调用 onFulfilled 方法,参数是当前的value
      - 并向 onRejectedCallbacks 数组中 push 一个方法,该方中去调用 onRejected 方法,参数是当前的reason
  2. 在resolve()方法中去循环 onFulfilledCallbacks 数组,并执行里面的函数,实参是 this.value
  2. 在reject()方法中去循环 onRejectedCallbacks 数组,并执行里面的函数,实参是 this.reason



then = (onFulfilled, onRejected) => {
        ...
        if (this.status === this.PENDING) {
            // pending状态push函数到onFulfilledCallbacks数组
            this.onFulfilledCallbacks.push(value => onFulfilled(value)) 
            this.onRejectedCallbacks.push(reason => onRejected(reason))
        }
    }
 resolve = (value) => {
        if (this.status === this.PENDING) {
            this.status = this.FULFILLED
            this.value = value
            this.onFulfilledCallbacks.forEach(fn => fn(this.value)) // 执行数组中的函数,并传入实参
        }
    }
reject = (reason) => {
        if (this.status === this.PENDING) {
            this.status = this.REJECTED
            this.reason = reason
            this.onRejectedCallbacks.forEach(fn => fn(this.reason))
        }
    }

如何保证执行顺序3

console.log(1)
new Promise((resolve, reject) => {
    console.log(2)
    setTimeout(() => {
        resolve()
        console.log(4)
    })
}).then(() =>  console.log(5))
console.log(3)



问题:上面代码输出 12354 , 而真正的promise应该输出  12345
分析:因为resolve()后面还有同步代码,要保证后面的同步代码先执行
解决:在向 onFulfilledCallbacks数组中push方法时,要再用 setTimeout包装,让resolve()后面的代码先执行



then = (onFulfilled, onRejected) => {
       ...
        if (this.status === this.PENDING) {
            this.onFulfilledCallbacks.push(value => {
                setTimeout(() => { // 再用setTimeout包装,保证resolve()后面的代码先于 then的回调函数 执行
                    onFulfilled(value)
                }, 0)
            })
            this.onRejectedCallbacks.push(reason => {
                setTimeout(() => {
                    onRejected(reason)
                }, 0)
            })
        }
    }

then的链式调用

  • 返回一个新的promise
  • 新的promise中参数函数的resolve的是onFufiled函数执行后返回的值
then = (onFulfilled, onRejected) => {
        if(typeof onFulfilled !== 'function') {
          onFulfilled = (value) => value
        }
        if(typeof onRejected !== 'function') {
          onRejected = (reason) => {
            throw reason
          }
        }
        const promise2 = new Promise((resolve, reject) => {
          if (this.status === Promise.FULFILLED) {
            setTimeout(() => {
              try {
                const x = onFulfilled(this.value) // 将onFulfilled函数的返回值作为resolve()的参数,传给新的 then() 方法
                resolve(x) // promise2的resolve的时机
              } catch(err) {
                reject(err) // promise2的reject的时机
              }
            })
          }
          if (this.status === Promise.REJECTED) {
            setTimeout(() => {
              try {
                const x = onRejected(this.reason)
                resolve(x)
              } catch (err) {
                reject(err)
              }
            })
          }
          if (this.status === Promise.PENDING) {
            this.onFulfilledCallbacks.push((value) => {
              setTimeout(() => {
                try {
                  const x = onFulfilled(value)
                  resolve(x)
                } catch(err) {
                  reject(err)
                }
              })
            })

            this.onRejectedCallbacks.push((reason) => {
              setTimeout(() => {
                try {
                  const x = onRejected(reason)
                  resolve(x)
                } catch(err) {
                  reject(err)
                }
              })
            })
          }
        })

        return promise2
      }

Promise.all()

------ 和 Promise.any()相反

  • 注意是静态方法,用于将多个promise实例包装成一个 ( 新的promise实例 )
  • 参数:(数组或者具有Iterator接口的数据类型,并且返回的每个成员都是promise实例
    参数是一个数组,成员是promise实例
    如果不是promise实例,会先调用Promise.resolve()转化成promise实例
  • 状态
    所有数组成员promise都变成 fulfilled时,才会变成 fulfilled
    只有一个成员的promise状态变成rejected,就会变成rejected
说明:
1. Promise.all()返回的是一个新的promise,即可以使用then获取resolve和reject的结果
2. 参数是一个数组或者具有Iterator接口的数据
3. 如果参数数组成员不是promise,就会被Promise.resolve()转成promise对象
4. resolve的时机是所有参数成员都变成fulfilled状态时
5. reject的时机是只要有一个rejected状态时


Promise.all = (promises) => {
    // 返回一个新的promise实例
    return new Promise((resolve, reject) => {
        const arr = []
        let count = 0 // 记录fulfilled状态的promise个数
        const promiseArr = Array.from(promises) // 参数除了数组还可以是具有Iterator接口的数据类型
        const len = promiseArr.length
        for (let i = 0; i < len; i++) {
            Promise.resolve(promiseArr[i]).then(value => { // 如果参数不是promise,会调用Promise.resolve()转成promise
                count ++ // 进入这里,表示成功的回调,即fulfilled状态
                arr[i] = value // 将该成功的promise装进数组
                if (count === len) { 
                    console.log(count, 'count')
                    resolve(arr)
                    // 如果count和数组总长度相等,说明都是fulfilled状态了
                    // 所有resolve的时机就是所有都变成fulfilled状态是resolve
                }
            }, reject)
        }
    })
}

const a = Promise.resolve(1)
const b = Promise.resolve(2)
const c = new Promise(resolve => {
    setTimeout(() => {
        resolve(33)
    })
})

Promise.all([a, b, c]).then(value => console.log(value, 'value'))

https://segmentfault.com/a/1190000012820865

https://juejin.im/post/5d0da5c8e51d455ca0436271

Promise.race()

Promise.race = (promises) => {
      return new Promise((resolve, reject) => {
        const promiseArr = Array.from(promises)
        const len = promises.length

        for(let i = 0; i < len; i++) {
          Promise.resolve(promiseArr[i]).then(value => {
            resolve(value) // 直接resolve第一个then是成功时的回调函数接收到的终值
          })
        }
      })
    }

推荐阅读更多精彩内容

  • Promise 对象 Promise 的含义 Promise 是异步编程的一种解决方案,比传统的解决方案——回调函...
    neromous阅读 6,355评论 1 54
  • Promise的声明 首先,promise肯定是一个类,我们就用class来声明。 由于new Promise((...
    oWSQo阅读 40评论 0 1
  • 原文地址https://fancierpj0.github.io/iPromise/ 目录 (づ ̄ 3 ̄)づ=> ...
    Cirs_冷峥子阅读 657评论 3 6
  • //本文内容起初摘抄于 阮一峰 作者的译文,用于记录和学习,建议观者移步于原文 概念: 所谓的Promise,...
    曾经过往阅读 810评论 0 7
  • 枝摆叶乱清秋寒,暮日归家形影单。未卜前程置度外,冰心傲骨待开颜。
    替父从军85阅读 110评论 2 5