手写实现Promise.all、Promise.race、Promise.finally

3,071 阅读3分钟

Promise.all()

Promise.all() 方法接收一个promise的iterable类型(注:Array,Map,Set都属于ES6的iterable类型)的输入,并且只返回一个Promise实例, 那个输入的所有promise的resolve回调的结果是一个数组。

这个Promise的resolve回调执行是在所有输入的promise的resolve回调都结束,或者输入的iterable里没有promise了的时候。它的reject回调执行是,只要任何一个输入的promise的reject回调执行或者输入不合法的promise就会立即抛出错误,并且reject的是第一个抛出的错误信息。

简单理解:所有都成功才成功,有一个失败就失败了

举例

Promise.all([123new Promise((resolve, reject) => {
 resolve('成功')
}), new Promise((resolve, reject) => {
 resolve('失败')
})
]).then(values => {
 console.log('成功', values)
}).catch(e => {
 console.log('e', e)
})
//结果
//成功 [ 1, 2, 3, '成功', '失败' ]

若我们将最后一个Promise的结果改为reject('失败'),即

Promise.all([123new Promise((resolve, reject) => {
 resolve('成功')
}), new Promise((resolve, reject) => {
 reject('失败')
})
]).then(values => {
 console.log('成功', values)
}).catch(e => {
 console.log('e', e)
})
//结果
// e '失败'

下面是手写Promise.all的方法

首先我们要判断Promise.all参数数组中的传值类型是不是一个Promise

function isPromise (value) {
 if (typeof value === 'function' || (typeof value === 'object' && value !== null)) {
   if (typeof value.then === 'function') {
     return true
  }
}
 return false
}

下面是Promise.all的实现方法

Promise.all = function (values) {
 return new Promise((resolve, reject) => {
   // result[3] = 2 result.length = 4,所以我们不能简单的用result.length来判定是否所有的Promise都执行完成,而应该采用计时器累加的方法
   let result = []
   let times = 0;
   const postSuccess = (i, value) => {
     result[i] = value
     if (++times === values.length) {
       resolve(result)
    }
  }
   for (let i = 0; i < values.length; i++) {
     const current = values[i]
     if (isPromise(current)) {
       current.then(value => {
         postSuccess(i, value)
      }).catch(e => {
         reject(e)
      })
    } else {
       postSuccess(i, current)
    }
  }
})
}

Promise.race()

Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。

简单理解:有一个成功或者失败就采用它的结果

虽然**Promise.race(iterable)** 使用的场景并不多,但是我们可以用它做一些简单的超时处理

race的译文为赛跑,即采用最快的那一个,race方法如果其中一个完成了,其他的还是会执行的,只是并没有采用它的结果

Promise.race = function (values) {
 return new Promise((resolve, reject) => {
   for (let i = 0; i < values.length; i++) {
     const current = values[i]
     if (isPromise(current)) {
       current.then(resolve, reject)//一旦成功就直接停止
    } else {
       resolve(current)
    }
  }
})
}

测试

let p1 = new Promise((resolve, reject) => {
 setTimeout(() => {
   resolve('成功')
}, 4000)
})
​
let p2 = new Promise((resolve, reject) => {
 setTimeout(() => {
   reject('失败')
}, 2000)
})
​
Promise.race([p1, p2]).then(values => {
 console.log('成功', values)
}).catch(e => {
 console.log('e', e)
})
// 可以修改p1和p2的结果测试

实际应用,图片加载超时问题,脚本加载超时问题

// 如何终止一个promise (中断promise) promise 超时
// race 的特点就是一个失败了就失败了,那我们可以构造一个自己的Promise和原Promise放在一起
let p = new Promise((resolve, reject) => {
 setTimeout(() => {
   resolve(123);
}, 3000);
})
function wrap (promise) {
 let abort;
 let newPromise = new Promise((resolve, reject) => {
   // 创建了一个promise,暴露一个终端方法
   abort = reject;
});
 let p = Promise.race([newPromise, promise]);
 p.abort = abort;
 return p;
}
let p1 = wrap(p);
setTimeout(() => {
 // 让这个promise 变成失败态
 p1.abort('超过2s了');
}, 2000);
p1.then(data => {
 console.log(data);
}).catch(err => {
 console.log(err);
});

Promise.prototype.finally()

finally() 方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式。

这避免了同样的语句需要在then()catch()中各写一次的情况。

简单理解:finally 的特点 无论如何都执行 ,但是如果返回的是一个promise需要等待这个promise之行完在继续向下执行

例如

let p1 = new Promise((resolve, reject) => {
 setTimeout(() => {
   resolve('成功了')
}, 2000)
}).finally(() => {// => then, 无论状态如何都会执行
 console.log('finally')
}).then(data => {
 console.log('data', data)
})
// finally
// data 成功了

finally后面还可以.then .then 所以finally本质上就是一个then

实现

Promise.prototype.finally = function (cb) {
  return this.then((data) => {
    // 如何保证Promise.then能够执行完毕
    return Promise.resolve(cb()).then((n) => data);
  }, (err) => {
    // Promise.resolve 目的是等待cb()后的Promise执行完成
    return Promise.resolve(cb()).then((n) => { throw err });
  })
}