!

最近忙着事业,又忙着晋升学习,还要搬家,好久没有输出了,不过还是要抽出边边角角的时间,分享一些好的内容,如果对你有所帮助,笔个芯再走吧

废话不多说,相信大多数人,一提到Promsie就能条件反射想到回调地狱,看了ES6官方晦涩难懂的解释,感觉一脸懵逼,害怕面试的时候被问到,答起来完全不知道自己在说什么,那么今天就来帮你解决掉这一大难题。

长篇预警!有点长,可以选择性观看。本篇主要带领你手写一个Promise源码,学完你就会发现:Promise没有你想象中的那么难,如果对Promise源码不是很清楚,还是推荐从头看,相信你认真从头看到尾,并且去实际操作了,肯定会有收获的。主要是代码部分有点多,不过好多都是重复的,不必担心

Promise出现的原因

promise出现的原因,说白了也就是promise解决了什么问题,这里我就简单说点吧:

Promise 是异步编程的一种解决方案,比传统的解决方案回调函数事件更合理和更强大,Promise的出现主要解决以下两个问题:

  1. 回调地狱: 某个异步操作需要等待之前的操作完成在继续执行, 当这样的需求多了以后, 使的代码进入无尽的嵌套,可读性降低不好维护
  2. 异步和同步之间的联系问题:当一个同步操作需要等待多个异步操作的结果, 这样会使得代码的逻辑变得相对来说比较复杂

myPromise的实现要点

  1. Promise 就是一个,实际上就是ES6提供的一个新的构造函数
  2. 通过new创建实例,接收一个函数作为参数,并且该函数中的代码默认是同步代码,会立即执行
  3. Promise 有三种状态,成功resolved失败rejected等待pedding
  4. Promise 状态一旦发生改变,就不能在改变,状态不可逆
  5. 用户可自定义成功的数据失败的原因
  6. Promise 实例拥有一个then方法。接受两个参数,一个成功回调,一个失败回调
  7. executor执行器在执行过程中,可能会抛出异常 throw new Error(),需要 try catch 捕获
  8. 当执行器中的是异步代码时(如定时器执行resolve),不是立即执行 状态不会变更,此时then方法中状态仍然是pending
  9. 一个 promise 实例可以被 then 多次,分别创建一个队列用来存放成功和失败回调事件
  10. 定时器执行时,判断状态为 pending,非立即执行,而是先存放 成功/失败 回调事件
  11. 等待状态变为成功或失败时遍历队列依次执行对应的onfulfilledonrejected,并且有执行顺序
  12. Promise 实现链式调用,返回的并不是this,而是一个新的promise实例, 因为原有的promise状态一旦发生改变,就不能再改变,否则不符合规范
  13. Promise 成功和失败的回调的返回值,可以传递给下一次的的then
  • 13.1. 返回的是普通值:不论then是成功还是失败。只要是普通值就传递到下一次then的成功中。(普通值包括:非错误非promise,包括对象)
  • 13.2. 报错或异常:一定会走到下一次then的失败中。如果离自己最近的then没有错误处理,会向下找
    • 特别注意: return new Error()返回的是错误对象,属于普通值走下一次then的成功,而throw new Error() 是抛出异常,需要try catch 会走下一次的失败
  • 13.3. 返回的是promise:会采用promise的状态,决定下一次then是成功还是失败,此处会有递归判断
  • 13.4. 总结: 如果返回一个普通值,除了promise ,就传递给下一个then的成功,如果返回一个失败的promise或者抛出异常,会走下一个then的失败
  1. Promise 值的穿透,当一个promise连续then 多次,并且then中没有返回任何值时,此时data会穿透至最后一个then中

    • 14.1 执行onfulfilled时,先判断是不是一个function,如果是就直接执行,反之就包一层函数
    • 14.2 执行onrejected时,先判断是不是一个function,如果是就直接执行,反之就抛出失败异常
  2. 既然promise是为了解决异步回调函数嵌套问题,那么可以通过promise的延迟对象来减少一层嵌套关系
  3. promiseA+ 规范测试
  4. 聊点规范以外的东东吧~~catch, finally,resolve,reject,all,race,就这么多

myPromise的实现

下面就来实现以上要点

myPromise — 实现简单的同步

myPromise.js

// 要点 3: 有三种状态, 相当于是常量,可以放在外面
const RESOLVED = 'RESOLVED'
const REJECTED = 'REJECTED'
const PENDING = 'PENDING'
// 要点 1: Promise 就是一个类
class Promise {
// 要点 2: 接收一个函数executor作为参数,立即执行
constructor(executor) {
this.status = PENDING
this.value = undefined // 要点 5: 用户可自定义 成功的数据
this.reason = undefined // 要点 5: 用户可自定义 失败的原因
let resolve = (value) => {
// 要点 4: 状态不可逆, 只有PENDING时,才可以改变状态
if (this.status === PENDING) {
this.value = value // 成功的数据
this.status = RESOLVED // 状态置为 RESOLVED
}
}
let reject = (reason) => {
// 要点 4: 状态不可逆, 只有PENDING时,才可以改变状态
if (this.status === PENDING) {
this.reason = reason // 失败的原因
this.status = REJECTED // 状态置为 REJECTED
}
} // 要点 7: 错误处理,抛出异常 throw new Error()
try {
executor(resolve, reject) // 立即执行
} catch(e) { // 抛出异常 直接失败
reject(e)
}
}
// 要点 6: 拥有一个then方法(实例上的方法)。接受两个参数回调函数
then(onfulfilled, onrejected) {
if(this.status === RESOLVED){
onfulfilled(this.value)
}
if(this.status === REJECTED){
onrejected(this.reason)
}
}
}
// Commonjs规范导出模块
module.exports = Promise

test.js:简单的同步操作验证

// 使用自己的promise,注释该行代码,就是原生promise
let Promise = require('./1.myPromise') let p = new Promise((resolve, reject) => {
resolve('成功')
// throw new Error('失败了')
// reject('失败')
}) p.then((data) => {
console.log(data)
}, (err) => {
console.log(err)
})

myPromise — 增加异步功能

在实现了简单的同步操作后,再来看看异步代码的执行,还是使用上面 test 中的案例,简单的改造一下,写一个定时器模拟异步环境,

test.js:增加异步功能

let Promise = require('./1.myPromise')

let p = new Promise((resolve, reject) => {
setTimeout(()=>{
resolve('成功')
},1000)
}) p.then((data) => {
console.log(data,1)
}, (err) => {
console.log(err)
})
// 一个实例可以then多次,执行结果会是一样的,因为状态已经固定
p.then((data) => {
console.log(data,2)
}, (err) => {
console.log(err)
})

myPromise.js

const RESOLVED = 'RESOLVED'
const REJECTED = 'REJECTED'
const PENDING = 'PENDING'
class Promise {
constructor(executor) {
this.status = PENDING
this.value = undefined
this.reason = undefined
// 要点 9: 分别创建一个`队列用来存放成功和失败回调事件
this.onfulfilledCallbacks = []
this.onrejectedCallbacks = []
let resolve = (value) => {
if (this.status === PENDING) {
this.value = value
this.status = RESOLVED
this.onfulfilledCallbacks.forEach(fn => fn()) // 要点 11: 状态变为成功时遍历队列依次执行
}
}
let reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason
this.status = REJECTED
this.onrejectedCallbacks.forEach(fn => fn()) // 要点 11: 状态变为失败时遍历队列依次执行
}
} try {
executor(resolve, reject)
} catch(e) {
reject(e)
}
}
then(onfulfilled, onrejected) {
if(this.status === RESOLVED){
onfulfilled(this.value)
}
if(this.status === REJECTED){
onrejected(this.reason)
}
// console.log(this.status) // PENDING
// 要点 8: 定时器执行resolve时,状态仍然是pending
if(this.status === PENDING){
// 要点 10: 一个 promise 实例可以被 then 多次,存放 成功回调事件
this.onfulfilledCallbacks.push(() => {
onfulfilled(this.value)
})
// 要点 10: 一个 promise 实例可以被 then 多次,存放 失败回调事件
this.onrejectedCallbacks.push(() => {
onrejected(this.reason)
})
}
}
}
// Commonjs规范导出模块
module.exports = Promise

myPromise — 链式调用(重难点,不好理解)

链式调用这一部分,比较绕,不太好理解,我的老师鼓励我,说:“书读百遍,其义自现”,哈哈哈,现在用来鼓励大家吧!!!

test.js

let Promise = require('./1.myPromise')

let p = new Promise((resolve, reject) => {
resolve('成功')
}) // 可以分别注释用例代码,进行效果演示
p.then((data) => {
return data // 用例1. 返回的是普通值: ‘成功’
// throw new Error() // 用例2. 报错或异常: 抛出异常,会直接走下一次then的失败
// return new Error() // 用例3. 返回的是普通值: error对象,需要特别注意
// return new Promise((s,j)=>s(1)) // 用例4. 返回的是promise: 会传递当前promise的已成功结果
// return new Promise((s,j)=>j(1)) // 用例5. 返回的是promise: 会传递当前promise的已失败结果
}).then((data) => {
console.log(data,2) // 执行结果:用例1, 用例3, 用例4
}, (err) => {
console.log(err,3) // 执行结果:用例2, 用例5
}) // 1. 返回的是普通值:不论then是成功还是失败。只要是普通值就传递到下一次then的成功中。(普通值包括:非错误非promise,包括对象)
// 2. 报错或异常:一定会走到下一次then的失败中。如果离自己最近的then没有错误处理,会向下找
// 3. 特别注意: return new Error()返回的是错误对象,属于普通值走下一次then的成功,而throw new Error() 是抛出异常,会走下一次的失败
// 4. 返回的是promise:会采用promise的状态,决定下一次then是成功还是失败,此处会有递归判断
// 5. 总结: 如果返回一个普通值,除了promise ,就传递给下一个then的成功,如果返回一个失败的promise或者抛出异常(try catch),会走下一个then的失败

myPromise.js

const RESOLVED = 'RESOLVED'
const REJECTED = 'REJECTED'
const PENDING = 'PENDING'
const resolvePromise = (promise2, x, resolve, reject) => {
if (promise2 === x) { // 防止自己等待自己 一直循环等待
// 原生返回:return reject(new TypeError('TypeError: Chaining cycle detected for promise #<Promise>'))
return reject(new TypeError('我们自己报的错:循环引用报错'))
}
let called; // 为了兼容其他promise 库符合a+规范,防止状态改变后再次被调用
// 考虑其他promise库兼容问题:判断x是不是promise: 不是null的对象 || 函数
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try { // 为防止then时 出现异常,Object.defineProperty
let then = x.then // 取x 的then 方法
/**
* 可能对象是{then:{}},还需要判断then是否是函数,
* 是函数就认为是promise,并调用then方法
*/
if (typeof then === 'function') {
// call第一个参数是this,后面的是成功回调 和 失败回调
// 如果成功回调返回值 y 仍然是promise,就继续递归解析promise,解析到返回值不是一个Promise类型为止
// promise2能否成功,是根据x的值来定的,x是promise,那么就要等到x完成,x完成后,如果返回y又是promise,那promise2又要等到y完成
then.call(x, y =>{
if (called) return
called = true
resolvePromise(promise2, y, resolve, reject)
}, e => {
if (called) return
called = true
reject(e)
})
} else {
// 否则就认为 then 是一个普通对象,成功即可
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e) // 出现异常,直接走失败逻辑
}
} else { // x 为普通值
resolve(x)
}
}
class Promise {
constructor(executor) {
this.status = PENDING
this.value = undefined
this.reason = undefined
this.onfulfilledCallbacks = []
this.onrejectedCallbacks = []
let resolve = (value) => {
if (this.status === PENDING) {
this.value = value
this.status = RESOLVED
this.onfulfilledCallbacks.forEach(fn => fn())
}
}
let reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason
this.status = REJECTED
this.onrejectedCallbacks.forEach(fn => fn())
}
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
} then(onfulfilled, onrejected) {
// 要点 12: then链式调用,返回的并不是this,而是一个`新的promise实例`
let promise2 = new Promise((resolve, reject) => { // new Promise执行器中的代码是立即执行,因此没有影响
if (this.status === RESOLVED) {
// 加定时器是因为在当前执行上下文中,不能获取promise2的值
setTimeout(() => {
// 要点13.2. 用于报错或抛出异常处理,以下同理
try {
// 要点13:存储第一次then的回调结果返回值,用于下次then的参数传递,以下let x 同理
let x = onfulfilled(this.value)
// 要点13.3: x 可能会是一个promise, 需要单独处理,
// 并将promise2、新的返回值:x 、成功回调resolve 失败回调reject 作为参数传递
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
// 要点13.2. 报错或抛出异常处理,直接走promise2的reject,以下同理
reject(e)
}
})
}
// 以下逻辑同理
if (this.status === REJECTED) {
setTimeout(() => {
try {
let x = onrejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
if (this.status === PENDING) {
// 暂存成功回调队列
this.onfulfilledCallbacks.push(() => {
setTimeout(() => {
try {
let x = onfulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
// 暂存失败回调队列
this.onrejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onrejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
})
return promise2 // 要点 12: 返回一个新的promise实例
}
} // Commonjs规范导出模块
module.exports = Promise

myPromise — 值的透传

Promise 值的穿透,当一个promise连续then多次 ,并且then中没有返回任何值时,此时data会穿透值最后一个then中

test.js

//原生方法:值的穿透 演示
let p = new Promise((resolve,reject)=>{
resolve(1)
// reject(2) // 同理
})
p.then().then().then(data=>{
console.log(data); // 1
})
p.then().then().then(null,err=>{
console.log(err); // 2
}) // ---------------------------------------------------------- //myPromise:基于以上几部分的实现,我们自己的promise是不支持穿透的,那么看一下演变过程,就很容易写出符合规范的透传
const Promise = require('./1.myPromise')
let p1 = new Promise((resolve,reject)=>{
resolve(1)
})
let p2 = new Promise((resolve,reject)=>{
reject(2) // 同理,
}) // 相当于, 啥都不写时,感觉像是默认定义了一个函数,不停向下传递,去实现一下吧~
p1.then(data => data)
.then(data => data)
.then(data =>{
console.log(data);
}) // 相当于, throw err,不停向下传递
p2.then(null,err=>{
throw err
}).then(null,err=>{
console.log(err);
throw err
}).then(null,err=>{
console.log(err);
})

myPromise.js

上面重复的代码是在是太多了,就不在赘述了,怕看着更晕了,来, 找到类中then方法定义的位置,加上下面两行代码,就可以完美实现成功及失败的数据透传

  ...
...
...
// 在这里这里...其他都不变
then(onfulfilled, onrejected) {
// onfulfilled 是函数就执行,不是函数包一层函数并直接返回数据
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : v => v;
// onrejected 是函数就执行,不是函数包一层函数并直接抛出错误
onrejected = typeof onrejected === 'function' ? onrejected : err => { throw err };
...
...
...

完美通过官方872条用例

为了测试myPromise库,必须公开一个非常小的适配器接口。可以说是promise的延迟对象defer,下文会详细介绍,各位稍安勿躁。

  1. 第一步:找到文件最下面。加上下面几行代码,
  /**
* ...
* 其余不变
* ...
*/
// 测试自己的写的promise 是否符合a+规范
// promise的延迟对象
Promise.defer = Promise.deferred = function () {
let dfd = {}
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject
})
return dfd;
} module.exports = Promise
  1. 第二步:安装测试包 npm install promises-aplus-tests -g
  2. 第三步:进入对应目录执行 promises-aplus-tests ./1.myPromise.js
  3. 如果你的测试结果和我图中标注一样,那么恭喜你,如果验证不通过,肯定是有一些不一样,注意看报错信息,修正一下就好了

myPromise的延迟对象defer用法

promise是为了解决异步回调函数嵌套问题,那么可以通过promise的延迟对象来减少一层嵌套关系

模拟数据准备,准备以下两个文件,

  1. name.txt:文件内容是:age.txt
  2. age.txt:文件内容18

    需求:首先通过fs模块读取到name中的内容(读到新的filepath),再去读取age.txt中的内容并输出

没有延迟对象的写法:

const fs = require('fs')
const Promise = require('./1.myPromise') function read(filepath){
// 想要read方法可以使用then方法,需要包一层new Promise
return new Promise((resolve,reject)=>{
fs.readFile(filepath,'utf8',function(err,data){
if(err) return reject(err)
resolve(data)
})
})
} read('./name.txt').then(data=>{
return read(data) // data = age.txt
}).then(data=>{
console.log(data); // data = age.txt
})

使用延迟对象的写法:

const fs = require('fs')
const Promise = require('./1.myPromise') function readDefer(filepath){
let dfd = Promise.defer() // 减少一层嵌套
fs.readFile(filepath,'utf8',function(err,data){
if(err) return dfd.reject(err)
dfd.resolve(data)
})
return dfd.promise
} readDefer('./name.txt').then(data=>{
return readDefer(data) // data = age.txt
}).then(data=>{
console.log(data); // data = age.txt
})

myPromise.catch

完成以上内容,就已经实现了Promise A+ 规范的全部内容,而我们常用的catchfinally并不属于 A+规范,同样可以实现一下,catch是属于实例上的方法,用于出错处理及捕获异常

演变过程:test.js

const Promise = require('./1.myPromise')

new Promise((resolve, reject) => {
reject('失败')
}).then(data => { // 第一个then没有错误处理方法,直接会传递到第二个then中
console.log(data);
}).then(null, err => {
console.log(err, 'errrrrr'); // 失败 errrrrr
}) // 将上述代码中null优化一下,于是就有了catch方法
new Promise((resolve, reject) => {
reject('失败')
}).then(data => {
console.log(data);
}).catch(err => {
console.log(err, 'errrrrr'); // 失败 errrrrr
})

实现:myPromise.js

class Promise{
// 重复代码省略
// constructor(){...}
// then(){...} catch(onrejected){ // 实例的catch
return this.then(null,onrejected)
}
}
/**
* 其余重复代码省略
*/

myPromise.finally

finally方法是属于实例上的方法.表示无论成功还是失败,都会执行,并且可以向下执行。理解起来有点点绕哈,建议多看几遍吧,

test.js

const Promise = require('./1.myPromise')

let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve('成功') // 1
reject('失败') // 2
}, 1000)
}).finally(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
// resolve(1000) // 3 不会采用promise成功的结果,但是会等待执行
reject(9000) // 4 但是返回一个失败的promise,会走catch,相当于throw new Error
}, 1000)
})
}).then(data => {
console.log(data); // 对应上述不同序号组合,输出结果有所不同,可自行组合实验,下方也会给大家作出总结
}).catch(e => {
console.log(e, 'catch');
}) /**
* 整理流程:
* 组合:
* 1+3:成功 + 成功 ,走then=>data 返回 1 返回的数据('成功')
* 1+4:成功 + 失败 ,走then=>err 抛出 4 返回的数据(9000)
* 2+3:失败 + 成功 ,走then=>err 抛出 2 返回的数据('失败')
* 2+4:失败 + 失败 ,走then=>err 抛出 4 返回的数据(9000)
*/

实现:myPromise.js

/**
* 1. finally 传递了一个方法 callback
* 2. finally 方法相当于一个then,特点就是在函数中返回一个promise,并会等待这个promise的完成
* 3. Promise.resolve 又会等待 callback执行完成
*/ class Promise{
// 重复代码省略
// constructor(){...}
// then(){...}
// catch(){...}
finally(cb){
return this.then((data)=>{ // 成功 || 失败 都会执行
// Promise.resolve 目的是等待cb() 后的promise完成
// 成功时调用finally,直接传递this实例成功的数据data,finally方法本身是没有参数的
return Promise.resolve(cb()).then(()=>data)
},(err)=>{
// 失败时调用finally,等待Promise.resolve(cb())的执行的结果,当cb结果为失败时,直接以失败的结果直接走catch
// 而只有在Promise.resolve成功时候,才会去执行.then(()=>{throw err}),抛出this实例reject的err信息
return Promise.resolve(cb()).then(()=>{throw err})
})
}
}
/**
* 其余重复代码省略
*/

myPromise.resolve

Promise.resolve() 方法会创造一个成功的promsie,属于类上的方法,通过className调用

演变过程:test.js

const Promise = require('./1.myPromise')

Promise.resolve(200).then(data => {
console.log(data); // 200
}) // 等价于
new Promise((resolve, reject) => {
resolve(200)
}).then(data => {
console.log(data); // 200
})

实现:myPromise.js

constructor(executor) {
/**
* 其余重复无改动
*/
let resolve = (value) => {
// 一个promise直接resolve 另一个promise时,会等待里面的promise执行完,返回成功的执行结果
if (value instanceof Promise) {
return value.then(resolve, reject)
}
if (this.status === PENDING) {
this.value = value
this.status = RESOLVED
this.onfulfilledCallbacks.forEach(fn => fn())
}
}
let reject = (reason) => {
// reject 一个promise时,不需要单独处理,失败就直接走失败的逻辑,不用处理其返回值
if (this.status === PENDING) {
this.reason = reason
this.status = REJECTED
this.onrejectedCallbacks.forEach(fn => fn())
}
}
/**
* 其余重复无改动
*/
}
static resolve(value) {
return new Promise((resolve, reject) => {
resolve(value)
})
}

myPromise.reject

Promise.reject() 方法会创造一个失败的promsie,属于类上的方法,通过className调用

演变过程:test.js

const Promise = require('./1.myPromise')

Promise.reject(100).then(null,err => {
console.log(err, 'errrrrr'); // 100 errrrrr
}) // 等价于
new Promise((resolve, reject) => {
reject(100)
}).then(data=>{
console.log(data);
},err => {
console.log(err, 'errrrrr'); // 100 errrrrr
})

实现:myPromise.js

class Promise{
/**
* 其余重复无改动
*/
static reject(reason) {
return new Promise((resolve, reject) => {
reject(reason)
})
}
}
/**
* 其余重复无改动
*/

myPromise.all

Promise.all()方法用于将多个 Promise 实例,包装并返回一个新的 Promise 实例p

p的状态由传递的多个 Promise 决定,分成两种情况。(官网是这样说的)

(1)只有所有promise实例的状态都变成fulfilledp的状态才会变成fulfilled,此时数组中promise的返回值组成一个数组,传递给p的回调函数。

(2)只要数组的中promise有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

test.js

const Promise = require('./1.myPromise')

Promise.all([
1, 2, 3,
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功')
}, 1000)
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功') // 情况1
// reject('失败') // 情况2
}, 1000)})
]).then(data => {
console.log(data); // 情况1. [ 1, 2, 3, '成功', '成功' ]
}).catch(err => {
console.log(err); // 情况2. 失败
})

myPromise.js

class Promise{
// 在原有代码基础上,添加一个静态方法
static all(promises){
return new Promise((resolve,reject)=>{
let result = []
let times = 0
const processSuccess = (index,val)=>{
result[index] = val
if(++times === promises.length){
resolve(result)
}
}
for (let i = 0; i < promises.length; i++) { // 并发。多个请求一起执行
let p = promises[i];
if(p && typeof p.then === 'function'){ // 判断p是不是promise实例,是则继续等待p的执行结果,处理p对应的成功和失败
p.then(data=>{
processSuccess(i,data)
},reject) // 如果其中一个promise失败了,直接执行失败回调,由于reject本身就是函数,故可简写
}else{ // 如果不是promise实例就直接调用成功,并存储该值
processSuccess(i,p)
}
}
})
}
}

myPromise.race

Promise.race()方法同样是将多个 Promise 实例,包装并返回一个新的 Promise 实例p

只要有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

(简单理解:race 赛跑,采用最快的那一个,race方法如果其中一个完成了,其他方法还是会执行完,只是并没有采用他的结果)

test.js

const Promise = require('./1.myPromise')
Promise.race([
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功')
}, 2000)
}),
new Promise((resolve, reject) => {
setTimeout(() => {
reject('失败')
}, 1000)
})
]).then(data => {
console.log(data);
}).catch(err => {
console.log(err); // 输出:失败,resolve 2s执行,reject 1s执行,reject更快
})

myPromise.js

class Promise{
static race(promises){
return new Promise((resolve,reject)=>{
// for循环执行,只要有一个状态发生改变,就直接将该实例的返回值 返回
for (let i = 0; i < promises.length; i++) {
let p = promises[i];
if(p && typeof p.then === 'function'){
p.then(data=>{
resolve(data)
},reject)
}else{
resolve(p)
}
}
})
}
}

myPromise终极版本

在我耐心终将耗尽之际,终于迎来了终极版

show一下myPromise全家桶,这里终极版本,为了大家看着方便,我就把注释全都取掉了,有不理解的可以返回去对应的标题查看解说,来咯

const RESOLVED = 'RESOLVED'
const REJECTED = 'REJECTED'
const PENDING = 'PENDING'
const resolvePromise = (promise2, x, resolve, reject) => {
if (promise2 === x) {
return reject(new TypeError('循环引用报错'))
}
if (x !== null && (typeof x === 'object' || typeof x === 'function')) { let called;
try {
let then = x.then
if (typeof then === 'function') {
then.call(x, y => {
if (called) return
called = true
resolvePromise(promise2, y, resolve, reject)
}, e => {
if (called) return
called = true
reject(e)
})
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
resolve(x)
}
}
class Promise {
constructor(executor) {
this.status = PENDING
this.value = undefined
this.reason = undefined
this.onfulfilledCallbacks = []
this.onrejectedCallbacks = []
let resolve = (value) => {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
if (this.status === PENDING) {
this.value = value
this.status = RESOLVED
this.onfulfilledCallbacks.forEach(fn => fn())
}
}
let reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason
this.status = REJECTED
this.onrejectedCallbacks.forEach(fn => fn())
}
} try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
then(onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : v => v;
onrejected = typeof onrejected === 'function' ? onrejected : err => {
throw err
};
let promise2 = new Promise((resolve, reject) => {
if (this.status === RESOLVED) {
setTimeout(() => {
try {
let x = onfulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
if (this.status === REJECTED) {
setTimeout(() => {
try {
let x = onrejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
if (this.status === PENDING) {
this.onfulfilledCallbacks.push(() => {
setTimeout(() => {
try {
let x = onfulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
this.onrejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onrejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
})
return promise2
}
catch (onrejected) {
return this.then(null, onrejected)
}
finally(cb) {
return this.then(
(data) => {
return Promise.resolve(cb()).then(() => data)
}, (err) => {
return Promise.resolve(cb()).then(() => { throw err })
})
}
static resolve(value) {
return new Promise((resolve, reject) => {
resolve(value)
})
}
static reject(reason) {
return new Promise((resolve, reject) => {
reject(reason)
})
}
static all(promises) {
return new Promise((resolve, reject) => {
let result = []
let times = 0
const processSuccess = (index, val) => {
result[index] = val
if (++times === promises.length) {
resolve(result)
}
}
for (let i = 0; i < promises.length; i++) {
let p = promises[i];
if (p && typeof p.then === 'function') {
p.then(data => {
processSuccess(i, data)
}, reject)
} else {
processSuccess(i, p)
}
}
})
}
static race(promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
let p = promises[i];
if (p && typeof p.then === 'function') {
p.then(data => {
resolve(data)
}, reject)
} else {
resolve(p)
}
}
})
}
} Promise.defer = Promise.deferred = function () {
let dfd = {}
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject
})
return dfd;
} module.exports = Promise

Click Me

既然你都看到这了,想必你肯定是感兴趣啦,希望这篇文章对你更加了解Promise有所帮助,后续工作之余还会分享更多实用的技巧和源码分析,点个赞加个关注再走呗

使用ES6中Class实现手写PromiseA+,完美通过官方872条用例的更多相关文章

  1. 手写一个Promise/A+,完美通过官方872个测试用例

    前段时间我用两篇文章深入讲解了异步的概念和Event Loop的底层原理,然后还讲了一种自己实现异步的发布订阅模式: setTimeout和setImmediate到底谁先执行,本文让你彻底理解Eve ...

  2. ES6中的模板字符串和新XSS Payload

    ES6中的模板字符串和新XSS Payload 众所周知,在XSS的实战对抗中,由于防守方经常会采用各种各样严格的过滤手段来过滤输入,所以我们使用的XSS Payload也会根据实际情况作出各种各样的 ...

  3. ES5和ES6中的继承 图解

    Javascript中的继承一直是个比较麻烦的问题,prototype.constructor.__proto__在构造函数,实例和原型之间有的 复杂的关系,不仔细捋下很难记得牢固.ES6中又新增了c ...

  4. ES6中的var let const应如何选择

    javascript世界里面的每个人都在说有关ECMAScript 6 (ES6,也称作ES 2015)的话题,对象的巨大变化 ( 类 , super() , 等), 函数 (默认参数等), 以及模块 ...

  5. ES6中Arguments和Parameters用法解析

    原文链接 译文 ECMAScript 6 (也称 ECMAScript 2015) 是ECMAScript 标准的最新版本,显著地完善了JS中参数的处理方式.除了其它新特性外,我们还可以使用rest参 ...

  6. ES6中的高阶函数:如同 a => b => c 一样简单

    作者:Sequoia McDowell 2016年01月16日 ES6来啦!随着越来越多的代码库和思潮引领者开始在他们的代码中使用ES6,以往被认为是"仅需了解"的ES6特性变成了 ...

  7. 深入理解 JavaScript 异步系列(3)—— ES6 中的 Promise

    第一部分,Promise 加入 ES6 标准 原文地址 http://www.cnblogs.com/wangfupeng1988/p/6515855.html 未经作者允许不得转载! 从 jquer ...

  8. ES6中的Symbol类型

    前面的话 ES5中包含5种原始类型:字符串.数字.布尔值.null和undefined.ES6引入了第6种原始类型——Symbol ES5的对象属性名都是字符串,很容易造成属性名冲突.比如,使用了一个 ...

  9. ES6中的Set和Map集合

    前面的话 在ES6标准制定以前,由于可选的集合类型有限,数组使用的又是数值型索引,因而经常被用于创建队列和栈.如果需要使用非数值型索引,就会用非数组对象创建所需的数据结构,而这就是Set集合与Map集 ...

  10. ES6中的模块

    前面的话 JS用"共享一切"的方法加载代码,这是该语言中最容出错且容易令人感到困惑的地方.其他语言使用诸如包这样的概念来定义代码作用域,但在ES6以前,在应用程序的每一个JS中定义 ...

随机推荐

  1. typora不支持mermaid 问题记录

    typora不支持mermaid 问题记录 注意: 使用不了最新版本js,目前我测的最高版本9.3,有些复杂的图表不能用,不过已经满足我使用的需求了.知足了 本文只做记录,如有问题请联系删除!!!感谢 ...

  2. 阿里云sdk调用

    slb调用 环境包安装 pip install alibabacloud_credentials  --trusted-host mirrors.aliyun.com  -i  http://mirr ...

  3. Machine Learning - 梯度下降

    一.梯度下降:目的是为了寻找到最合适的 $w$ 和 $b$ ,让成本函数的值最小 \[w = w - α\frac{\partial J(w,b)}{\partial w} \] \[b = b - ...

  4. .NETCore Nuget 发布包含静态文件 content file

    .NETCore 在.csproj引用资源中标记pack配置 <pack>true</pack>1例如 <ItemGroup> <Content Includ ...

  5. 支持表格识别,PaddleOCRSharp最新发布

    PaddleOCRSharp 2.3.0已经发布nuget包. 项目开源地址:https://gitee.com/raoyutian/paddle-ocrsharp 2.3.0更新内容: 1.增加表格 ...

  6. Jenkins获取gitlab源代码

    Jenkins获取gitlab源代码 Jenkins权限获取 在日常工作做由于Jenkins启动用户是Jenkins,在执行脚本时系统命令是无法让Jenkins执行的,如果需要Jenkins权限有两种 ...

  7. iPhoneX 适配总结

    一.iPhoneX适配第一步,根据iPhoneX的屏幕像素大小,引入对应的启动图,告诉系统,app兼容iPhoneX 需要在launchimage中引入一张 1125*2436的png,app将默认展 ...

  8. tomcat部署Jenkins

    安装环境 jdk 1.8 tomcat 9.0 jenkins 2.290 准备工作 安装好Tomcat,8080端口启动 安装好jdk,配置好环境变量 ECS服务器安全组放开8080端口 关闭防火墙 ...

  9. winform遍历控件的Controls.OfType<>方法

    页面TextBox控件较多时,可以使用类似的名字,方便遍历时整体的修改,可以使用Controls.OfType<TextBox>()获取所有文本框: 需要引用命名空间System.Linq ...

  10. 增补博客 第一篇 python 简易带参计算器

    设计一个简易的参数计算器.[输入格式]第一行输入待计算的带变量参数的计算式第二行输入各变量参数的赋值序列[输出格式]输出带变量参数的计算式的计算结果[输入样例]a+ba=1,b=10[输出样例]11 ...