JS个人学习(13)——Promise.all、Promise.race等各类方法实现

344 阅读6分钟

原型上的方法

Promise.prototype.then

Promise.prototype.catch

这两个方法都在 JS个人学习——一个符合A+规范的Promise实现中写过。 包括下述resolvePromise方法都在这里写过。

Promise.prototype.finally

finally方法的作用于promise对象执行后,不管成功还是失败,都会执行finally的回调函数,并且这个回调函数没有参数,最后finally返回一个新的promise实例,它的状态跟之上一个promise实例的状态相同。

原理:

finally方法实际上就是补充一个正常的then方法,这个then方法两个参数都有传入,唯一不同的就是finally返回的promise实例的状态跟调用它的promise实例的状态是一致的,而直接用then后返回的promise实例的状态不一定跟调用它promise实例状态相同:

var promise1 = new Promise((resolve, reject) => {
  reject('error')
})
var promise2 = promise1.then(res => {
}, reason => {
})
setTImeout(() => {
  console.log(promise1) // Promise{<rejected>: 'error'}
  console.log(promise2) // Promise{<fulfilled>: undefined}
})



var promise1 = new Promise((resolve, reject) => {
  reject('error')
})
var promise2 = promise1.finally(() => {
})
setTimeout(() => {
  console.log(promise1) // Promise{<rejected>: 'error'}
  console.log(promise2) // Promise{<rejected>: 'error'}
})

根据上述代码可以看出用finallythen之间的一些区别,因此在finally实现的时候只要模拟正常链式调用then方法,并且补全它的两个参数,然后根据上一个promise实例的状态进行返回新实例。

简单来说,就是在finally中执行了一个方法,其他的都保持上一个promise实例的样子不变。

Promise.resolve('success').then(res => {
  return res
})
.finally(() => {
  // 回调函数内容
})

// finally的执行就跟加了下列代码差不多,只不过就是要将finally的回调函数放到resolve中执行
.then(value => {
  return Promise.resolve().then(() => {
    return value
  })
}, reason => {
  retutn Promise.resolve().then(() => {
    throw reason
  })
})

实现:

// finally只接受一个回调函数,因此只要传一个callback就行
Promise.prototype.finally = function (callback) {
  return this.then((value) => {
    /**
     这里在Promise.resolve中执行callback
     这里返回一个新的promise是为了让then中返回的promise的状态与其统一
    */
    return Promise.resolve(callback()).then(() => {
      return value;
    });
  }, (err) => {
    return Promise.resolve(callback()).then(() => {
      throw err;
    });
  });
}

对象上的方法

Promise.resolve()

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

  1. 如果value是个thenable对象,返回的promise的状态会跟着thenable的状态保持一致。
  2. 如果value本身就是一个promise对象,那么就将其原封不动地返回。
  3. 其他情况下,直接返回成功状态的promise对象。

实现:

Promise.resolve = function (value) {
    // 对应情况2
    if (value instanceof Promise) {
        return value
    }
    return new Promise((resolve, reject) => {
        // 对应情况1
        if (value && value.then && typeof value.then === 'function') {
            // 用setTimeout保证x.then是异步执行
            setTimeout(() => {
                value.then(resolve, reject)
            })
        } else {
            // 直接返回,对应情况3
            resolve(value)
        }
    })
}

Promise.reject()

Promise.reject()方法与Promise.resolve()不同,只需直接将参数原封不动放到reject中作为后续方法的参数就行。

实现:

Promise.reject = function (reason) {
    // 直接将reason放到reject中返回就行
    return new Promise((resolve, reject) => {
        reject(reason)
    })
}

Promise.all()

Promise.all()需要一个由promise实例组合成数组作为参数,并将其包装成一个新的promise实例返回,并且成功和失败的返回值是不同的,成功的时候返回的是一个数组结果,而失败的时候则返回最先被reject的值

使用方式:

var p1 = new Promise((resolve, reject) => { resolve('success1') })
var p2 = new Promise((resolve, reject) => { resolve('success2') })
var p3 = Promise.resolve('success3')
var result = Promise.all([p1, p2, p3])
console.log(result)  // Promise {<fulfilled>: Arrary(3)}
var p1 = new Promise((resolve, reject) => { resolve('success1') })
var p2 = new Promise((resolve, reject) => { reject('error2') })
var p3 = Promise.resolve('success3')
var result = Promise.all([p1, p2, p3])
console.log(result)  // Promise {<reject>: 'error2'}

其他说明:

  1. 如果传入的参数是一个空的可迭代对象,那么此promise对象回调完成(resolve),这种情况下是同步执行的,其他情况都是异步的。
  2. 如果传入的参数promises不包含任何promise,则将其值直接原封不动放到结果数组中返回。
  3. 参数promises中所有promise都执行完成或者其中有状态为'rejected'时,Promise.all执行完成
  4. 如果参数promises中有一个promise失败,那么Promise.all返回的promise对象的状态为'rejected',并且结果为失败的reason,此时返回的promise结果不是一个数组,而是promises中最先返回'rejected'状态的返回值
  5. Promise.all返回数组时,数组中对应的结果要与传入的promise顺序保持一致。

实现:

Promise.all = function (promises) {
  // 返回一个promise对象
  return new Promise((resolve, reject) => {

    // 用index记录是否全部执行成功
    var index = 0

    // 用result,存放执行结果,并且最后执行完成时返回
    var result = []

    // 这里需要判断如果传入的promises为空数组,那么直接返回,对应情况1
    if (promises.length === 0) {
      return resolve(result)
    }

    // 遍历传入的参数,来获取每个promise的执行结果,这里的需要使用let,因为then方法的调用是异步的,
    // 不使用let的话会导致之后then中的回调所用到的i都为相同值,用let创建块级作用域避免此问题
    for (let i = 0; i < promises.length; i++) {
      
      // 放到Promise.resolve()中执行是因为promises[i]有可能不是promise对象
      Promise.resolve(promises[i]).then(value => {
        /**
         这里要使用result[i]来存值,以保证返回的结果的顺序跟传入的promise顺序相同,对应情况5,
         因为这里then的调用是异步的,为了防止在异步状态下返回结果顺序不一致,因此不使用reslut.push(value)
         */
        result[i] = value

        // 通过index值判断是否全部执行完毕,如果执行完毕,那么将result作为参数执行resolve
        if (++index == promises.length) {
          resolve(result)
        }
      }, reason => {
        /** 
         这里就是当promis执行时有一个是返回rejected状态时,
         那就直接返回第一个失败的状态和结果,
         因此不必像上面判断成功那样子来判断是否所有promise成功
        */
        reject(reason)
      })
    }
  })
}

测试代码:

var promise1 = new Promise((resolve, reject) => {
    resolve('success1')
})
var promise2 = 66;
var promise3 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 100, 'success2')
})

Promise.all([promise1, promise2, promise3]).then(function(values) {
    console.log(values)
},(err)=>{
    console.log(err)
});

var p = Promise.all([])
var p2 = Promise.all([2021, "wyt"])
console.log(p)
console.log(p2)
setTimeout(function(){
    console.log('hello world')
    console.log(p2)
})

Promise.race()

Promise.race()Promise.all()一样是传入一个数组作为参数,并返回一个promise对象,但是它的状态是由第一个执行完成的promise状态来决定的,无论是fulfilled还是rejected。

使用方式:

var p1 = new Promise((resolve, reject) => { resolve('success1') })
var p2 = new Promise((resolve, reject) => { resolve('success2') })
var p3 = Promise.resolve('success3')
var result = Promise.race([p1, p2, p3])
console.log(result) // Promise {<fulfilled>: 1}

实现:

// 相对来说race的实现比all简单,不需要判断promises是否完全执行完成,
// 只要返回最快执行完成的promise实例的状态和结果就行。
Promise.race = function (promises) {
  return new Promise((resolve, reject) => {
    for (let i = 0; i < promises.length; i++) {
      if (promises.length === 0) {
          resolve(promises)
      }
      Promise.resolve(promises[i]).then(value => {
        resolve(value)
      }, reason => {
        reject(reason)
      })
    }
  })
}

Promise.allSettled()

Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilledrejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。

实现:

// Promise.allSettled()方法的实现跟Promise.all()大同小异,只有在一些细节上做一下处理就行
Promise.allSettled = function (promises) {
  return new Promise((resolve, reject) => {
    var index = 0
    var result = []
    if (promises.length === 0) {
      return resolve(result)
    }
    for (let i = 0; i < promises.length; i++) {
      Promise.resolve(promises[i]).then(value => {
        // 细节一:跟Promise.all()方法不同的是需要将状态跟返回值放到对象中返回
        result[i] = {
            state: 'fulfilled',
            value: value
        }
        if (++index == promises.length) {
          resolve(result)
        }
      }, reason => {
        result[i] = {
            state: 'rejected',
            reason: reason
        }
        // 细节二:在reject时也要判断promises是否全部执行完成
        // allSettled永远都是返回的promise对象永远都是resolve
        if (++index == promises.length) {
          resolve(reason)
        }
      })
    }
  })
}

Promise.any()

Promise.any()方法跟Promise.all()方法刚好相反,只要有一个promise状态为成功就返回其对应的状态和结果

实现:

Promise.any = function (promises) {
  return new Promise((resolve, reject) => {
    var index = 0
    var result = []
    if (promises.length === 0) {
      return resolve(result)
    }
    for (let i = 0; i < promises.length; i++) {
      Promise.resolve(promises[i]).then(value => {
        // 细节一:跟Promise.all()方法不同的是这里只要有一个执行成功就直接返回
        resolve(value)
      }, reason => {
        result[i] = reason
        // 细节二:在reject时判断是否全部都执行了reject,如果这样就要返回失败状态并且报错
        if (++index == promises.length) {
          reject(new Error('All promises were rejected'))
        }
      })
    }
  })
}