Promise.all和Promise.race源码实现

4,529 阅读7分钟

Promise.all

Promise.all 方法

  • 接收一个 Promise 数组
  • 返回一个 Promise,用p缓存

在传给 Promise.all 的所有 Promise 都兑现后,p也会随之兑现并按照传入顺序返回各 Promise 兑现的结果数组

但只要有一个 Promise 被拒绝,那么 p 就会立即以该 Promise 的拒绝理由确定为被拒绝。

同时异步请求多个数据的时候,即并行,就会使用用all

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p1')
  }, 1000);
})

let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p2')
  }, 2000);
})

// Prmose.all 
console.time('cost')
Promise.all([p1, p2]).then(data => {
  console.log(data);
  console.timeEnd('cost')
})

输出结果是

[ 'p1', 'p2' ]
cost: 2026.419ms

代码实现

调用的使用是通过Promise.all,说明这个方法是在类上的,而不是在实例上的,故这里使用static

class Promise{
  //...
  static all(proArr) {
    return new Promise((resolve, reject) => {
      // ...
    })
  }
}

我们最后返回的是proArr里面各个promise返回的值,而且还是按promise的顺序存储的数组

所以我们设置一个数组ret = []来缓存数据

我们遍历proArr执行里面的promise

static all(proArr) {
  return new Promise((resolve, reject) => {
    let ret = []
    let done = (i, data) => {
      ret[i] = data
    }
    for (let i = 0; i < proArr.length; i++) {
      proArr[i].then(data => done(i,data) , reject)
    }
  })
}

这里我们需要一个辅助函数done来完成每个 promise成功返回的值的存储

那为什么不直接

proArr[i].then(data => ret[i] = data, reject)

因为我们还需要一个变量来告诉我们ret里数据是否已经装满了

装满的时候我们就执行resolve(ret)发送出去

static all(proArr) {
  return new Promise((resolve, reject) => {
    let ret = []
    let count = 0
    let done = (i, data) => {
      ret[i] = data
      if(++count === proArr.length) resolve(ret)
    }
    for (let i = 0; i < proArr.length; i++) {
      proArr[i].then(data => done(i,data) , reject)
    }
  })
}

为什么要使用一个计数变量count呢?我们来写一个没有使用count的错误写法

易错点

这也是容易出bug的原因

static all(proArr) {
  return new Promise((resolve, reject) => {
    let ret = []
    let done = (i, data) => {
      ret[i] = data
      if(i === proArr.length -1) resolve(ret)
    }
    for (let i = 0; i < proArr.length; i++) {
      proArr[i].then(data => done(i,data) , reject)
    }
  })
}

我们使用i等于proArr.length -1作为数据装满时的触发条件

看起来就像执行完proArr最后一个promise时我们就执行resolve(ret)

这是因为没有理解Promise.then的意义,then方法是往promise绑定方法

这绑定的方法不是立即执行的,是then所属的promise成功或者失败后完成的

那如果我们按上面这种写法,如果proArr[0]要5s后才完成,即5秒后才会执行

done = (0, data) => {
  ret[0] = data
  if(i === proArr.length -1) resolve(ret)
}

proArr[2]是1秒后就完成了,那1秒后就会执行

done = (2, data) => {
  ret[2] = data
  if(i === proArr.length -1) resolve(ret) 
}

此时因为proArr.length - 1等于2,所以就执行了resolve(ret)

然而我们知道此时的proArr[0]还没执行,即我们还没往ret[0]里面添加数据呢

使用count的原因就在这里,proArr中任意一个填充完数据后,即执行++count

当最后一个执行++count时,++count === proArr.length于是就可以执行resolve(ret)

柯里化

上面的需求就很符合柯里化的写法

static all(proArr) {
  return new Promise((resolve, reject) => {
    let done = (function (len, cb) {
      const ret = []
      let count = 0
      return function (i, data) {
        ret[i] = data
        if (++count === len) cb(ret)
      }
    })(proArr.length, resolve)

    for (let i = 0; i < proArr.length; i++) {
      proArr[i].then(data => done(i, data), reject)
    }
  })
}

当然看起来也差不多,当然,因为这么写的话,我们可以把函数外移,那逻辑代码就很清晰了

Promise{
  // ...
  static all(proArr) {
    return new Promise((resolve, reject) => {
      let done = currying(proArr.length, resolve)
      for (let i = 0; i < proArr.length; i++) {
        proArr[i].then(data => done(i, data), reject)
      }
    })
  }
}

function currying(len, cb) {
  const ret = []
  let count = 0
  return function (i, data) {
    ret[i] = data
    if (++count === len) cb(ret)
  }
}

Promise.race

同all一样接受一个数组,该数组里面都是promise对象。

区别在于,结果是返回最先返回的promise数据,这里的数据是最先返回的单个数据。有点像是比赛,谁先到就用谁。

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p1')
  }, 1000);
})

let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p2')
  }, 2000);
})

// Promise.race
console.time('cost')
Promise.race([p1, p2]).then(data => {
	console.log(data);
	console.timeEnd('cost')
})

输出结果是

p1
cost: 1014.655ms

当有调用的接口不稳定的时候,我们可以取多个,先获取到哪个就使用哪个。

还能拿来给promise做个定时器,如果在规定时间没到,我们就执行一个reject

let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p2')
  }, 2000);
})

function tiemout(delay) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject('tiemout')
    }, delay);
  })
}

Promise.race([p2, tiemout(500)]).then(res => {
  console.log(res);
}).catch(err => {
  console.log(err);
})

如果过了500ms时p2还没有获得数据回来,那么timout就会执行reject,报 timeout

那实现起来也很方便

static race(promiseAry) {
  return new Promise((resolve, reject) => {
    for (let i = 0; i < promiseAry.length; i++) {
      promiseAry[i].then(resolve, reject)
    }
  })
}

这么简单得益于promise的状态只能改变一次,即resolve和reject都只被能执行一次,我们把返回的promie里的resolve和reject方法放在了promiseAry每一个promise的成功或者失败回调里面,当其中任意一个成功或失败后就会调用我们传进去的resolve和reject,一旦调用了,就不会再次调用。

catch && resolve && reject

catch(onRejected) {
  return this.then(null, onRejected)
}
static resolve(value) {
  return new Promise((resolve, reject) => resolve(value))
}

static reject(reason) {
  return new Promise((resolve, reject) => reject(reason))
}

上面的实现就很简单的,没什么好讲的,这里可以说一个catch

我们平时在写then时,有两个参数,一个成功一个失败,连在一起写,挺容易看混的,所以有一种实践就是then方法里只写成功处理函数,然后后面添加一个catch来处理失败

Promise.reject('失败')
  .then(data => { console.log(data) })
  .catch(reason => { console.log(reason) })

这里运用的就是,我们在then源码实现时,如果rejected不是一个函数就会把错误往后抛的原理。

源码

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('循环引用'));
  }
  let then
  let called = false
  if (x instanceof Promise) {
    if (x.status == PENDING) {
      x.then(
        y => resolvePromise(promise2, y, resolve, reject),
        r => reject(r)
      )
    } else x.then(resolve, reject);
  } else if (x != null && ((typeof x == 'object' || typeof x == 'function'))) {
    try {
      then = x.then;
      if (typeof then == 'function') {
        then.call(
          x,
          y => {
            //防止promise会同时执行成功和失败的回调
            //如果promise2已经成功或失败了,则不会再处理了
            if (called) return;
            called = true;
            resolvePromise(promise2, y, resolve, reject);
          },
          r => {
            //防止promise会同时执行成功和失败的回调
            //如果promise2已经成功或失败了,则不会再处理了
            if (called) return;
            called = true;
            reject(r);
          });
      } else {
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    resolve(x);
  }
}
// function gen(times, cb) {
// 	let ret = []
// 	let count = 0
// 	return function (i, data) {
// 		ret[i] = data
// 		if (++count === times) {
// 			cb(ret)
// 		}
// 	}
// }
class Promise {
  constructor(executor) {
    // 设置状态
    this.status = PENDING
    this.value = undefined
    // 定义存放 成功后 执行的回调数组
    this.onResolvedCallbacks = []
    // 定义存放 失败后 执行的回调数组
    this.onRejectedCallbacks = []

    let resolve = data => {
      let timer = setTimeout(() => {
        clearTimeout(timer)
        if (this.status === PENDING) {
          this.status = FULFILLED
          this.value = data
          this.onResolvedCallbacks.forEach(cb => cb(this.value))
        }
      })
      }
    let reject = reason => {
      let timer = setTimeout(() => {
        clearTimeout(timer)
        if (this.status === PENDING) {
          this.status = REJECTED
          this.value = reason
          this.onRejectedCallbacks.forEach(cb => cb(this.value))
        }
      })
      }

    try {
      executor(resolve, reject)
    } catch (error) {
      reject(error)
    }
  }
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected == 'function' ? onRejected : reason => { throw reason };
    let promise2

    if (this.status === FULFILLED) {
      promise2 = new Promise((resolve, reject) => {
        let timer = setTimeout(() => {
          clearTimeout(timer)
          try {
            let x = onFulfilled(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
        })
    }
    if (this.status === REJECTED) {
      promise2 = new Promise((resolve, reject) => {
        let timer = setTimeout(() => {
          clearTimeout(timer)
          try {
            let x = onRejected(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
        })
    }
    if (this.status === PENDING) {
      promise2 = new Promise((resolve, reject) => {
        this.onResolvedCallbacks.push(value => {
          try {
            let x = onFulfilled(value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
        this.onRejectedCallbacks.push(reason => {
          try {
            let x = onRejected(reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      })
    }

    return promise2
  };

  catch(onRejected) {
    return this.then(null, onRejected)
  }
  static resolve(value) {
    return new Promise((resolve, reject) => resolve(value))
  }

  static reject(reason) {
    return new Promise((resolve, reject) => reject(reason))
  }

  // static all(promiseAry) {
  // 	return new Promise((resolve, reject) => {
  // 		let done = gen(promiseAry.length, resolve)
  // 		for (let i = 0; i < promiseAry.length; i++) {
  // 			promiseAry[i].then(
  // 				data => { done(i, data) },
  // 				reject)
  // 		}
  // 	})
  // }
  static all(promiseAry) {
    return new Promise((resolve, reject) => {
      let ret = []
      let count = 0
      for (let i = 0; i < promiseAry.length; i++) {
        promiseAry[i].then(
          data => {
            ret[i] = data
            if (++count === promiseAry.length) {
              resolve(ret)
            }
          },
          reason => reject(reason)
        )
      }
    })
  }

  static race(promiseAry) {
    return new Promise((resolve, reject) => {
      for (let i = 0; i < promiseAry.length; i++) {
        promiseAry[i].then(resolve, reject)
      }
    })
  }
}





// 测试
Promise.deferred = Promise.defer = function () {
  var defer = {};
  defer.promise = new Promise(function (resolve, reject) {
    defer.resolve = resolve;
    defer.reject = reject;
  })
  return defer;
}
try {
  module.exports = Promise
} catch (e) {
}