- 2021/04/10 复习
- 2021/10/11 复习优化
导航
[深入01] 执行上下文
[深入02] 原型链
[深入03] 继承
[深入04] 事件循环
[深入05] 柯里化 偏函数 函数记忆
[深入06] 隐式转换 和 运算符
[深入07] 浏览器缓存机制(http缓存机制)
[深入08] 前端安全
[深入09] 深浅拷贝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模块化
[深入13] 观察者模式 发布订阅模式 双向数据绑定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手写Promise
[深入20] 手写函数
[部署01] Nginx
[部署02] Docker 部署vue项目
[部署03] gitlab-CI
[源码-webpack01-前置知识] AST抽象语法树
[源码-webpack02-前置知识] Tapable
[源码-webpack03] 手写webpack - compiler简单编译流程
[源码] Redux React-Redux01
[源码] axios
[源码] vuex
[源码-vue01] data响应式 和 初始化渲染
[源码-vue02] computed 响应式 - 初始化,访问,更新过程
2024-02-29 更新 Promise._all
- Example one
- There is a question that the orders of the parameters and return values are not same.
Example twosolve the question of the above.
例一
- There is a question that the orders of the parameters and return values are not same.
- 存在顺序问题,参数 和 返回值 顺序不一致
---
Promise._all = (args) => {
return new Promise((resolve, reject) => {
const result = [];
const arr = Array.from(args);
arr.forEach((_promise, i) => {
Promise.resolve(_promise).then((p) => {
// AAAA
// TIPS: attention, there is a question of order. Please compare the original function with the implementation function.
result.push(p);
if (result.length === arr.length) resolve(result);
}, reject);
});
});
};
例二
- Solve the above question
---
Promise._all = (args) => {
return new Promise((resolve, reject) => {
const result = [];
const arr = Array.from(args);
let index = 0;
arr.forEach((_promise, i) => {
Promise.resolve(_promise).then(
(p) => {
result[i] = p;
index++; // The variable of index is used to 【 ensure 】 that the parameters and return values of the Promise._all function are in the same order.
if (index === arr.length) resolve(result);
},
reject
);
});
});
};
前置知识
(1) 一些单词
race:比赛,竞赛
Settled:结束
execute:执行
executor:执行者
detected:检测
(2) 同步任务 宏任务 微任务
1. 常见的 宏任务 和 微任务
- 宏任务
- setTimeout
- setInterval
- setImmediate
- requestAnimationFrame - 在浏览器下一帧渲染前执行
- 微任务
- promise
- process.nextTick
- MutationObserver ------ 观测DOM变化
2. 同步任务 宏任务 微任务 的执行顺序
--> 同步任务
--> 清空微任务队列: 所有的微任务
--> 执行宏任务队列中的第一个宏任务
--> 清空微任务队列: 所有的微任务
--> 执行宏任务队列中的第一个宏任务
--> 直到所有任务都执行完毕
- 宏任务和微任务执行相当于:两层嵌套的for循环
- for() {// 宏任务 for() { // 微任务} }
3. 案例
- 请查看
- test1.html
- test2.html
- test3.html
4. 扩展
- 问题:为什么 process.nextTick() 在 Node 中可以任意阶段优先执行?
- 回答:应为 process.nextTick() 是一个微任务,其他阶段都是执行的宏任务比如setImmediate
5. async ... await
- async
- 作用:始终返回一个promise对象
- 1. 如果返回值不是promise对象:如果async函数的返回值不是promise对象,这回通过 Promise.resolve() 包装成fulfilled状态的 promise 对象
- 2. 若果没有返回值时:如果async函数没有返回值,则会返回 Promise.resolve(undefined)
- 执行顺序
- ( !!!重要!!! ) 当 async 函数中有 await 时,执行完await后面的表达式,async会让出当前线程,将await表达式后面的代码放入 ( 微任务队列 ) 中,继续执行 async 函数后面的代码
- await
- 1. 出现的地方:await 只能用于 async 函数中
- 注意:2022.5.12 最新的语法可以使用 顶层await了,在vue3中已经使用到
- 2. await等待的是什么?
- 如果await等待的是一个:( promise对象 ),则await会 ( 阻塞 ) 后面代码的执行,直到promise状态变成resolve,但是 !!!!! async函数不会阻塞
- 当然await后面的表达是可以是任意类型的值,不一定非要是promise对象,不是promise时,await等待的就是紧跟的表达是的返回值
- 特点
- 2022.05.12
- 在 async 函数中有 await 时,执行完 (await后面的表达式) 后,会将await后面的表达式 后面的代码放入微任务队列,并跳出 async 函数,执行async函数后面的代码
- 案例
- test3.html
- test4.html
- test5.html
(3) promise复习
-
方法
- promise.then() ---------------------------- 返回新的promise
- promise.catch() --------------------------- 返回新的promise
- promise.finally() -------------------------- 不管状态如何都会执行
- promise.all() ------------------------------ 所有resolve则fulfilled,一个reject则rejected
- promise.any() ---------------------------- 一个resolve则fulfilled,所有reject则rejected
- promsie.race() ---------------------------- 第一个resolve则fulfiled,第一个reject则rejected
-
特点
- 对象的状态不受外界影响,只有异步操作的结果才可以决定当前是哪一种状态,任何其他操作都不能改变这个状态
- 状态一旦改变就不会再变,任何时候都可以得到这个结果
-
缺点
- 无法取消promise,一旦新建就会立即执行,中途无法取消
- 如果不设置回调,内部抛出的错误,不会反应到外部
- 当处于pending状态时,无法得知当前进展到哪一个阶段 ( 刚开始?即将结束?)
-
Promise的用法
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
promise.then(function(value) {
// success
}, function(error) {
// failure
});
说明:
(1) Promise构造函数接受一个 ( 函数 ) 作为参数
(2) ( 参数函数 ) 又接受两个函数作为参数 ( resolve函数 ) 和 ( reject函数 )
(3) resolve()
- 函数是在状态由 pending->fulfilled 时,即成功时调用,并将异步操作的结果作为参数传递出去
- resolve()函数的参数,除了是正常的 ( 值 ) 以外,还可以是一个 ( promise实例 )
(4) reject()
- 函数是在状态由 pending->rejected时,即失败时调用,并将异步操作报出的错误作为参数传递出去
- reject()函数的参数,通常是 ( Error对象 ) 的实例
(5) then() 方法接受两个函数作为参数
- 第一个参数函数在resolve时被调用
- 第二个参数函数在reject时被调用,参数分别是终值和拒因,第二个参数函数可选
- resolve()
- resolve()和reject()两个函数并不会终止promise,后面的代码还是会执行
- 通常用 return resolve() return reject() 这样的方式结束promise构造函数代码的运行
- resolve() 函数的参数是一个promise实例的情况
let count = 1
setInterval(() => {console.log(count++)}, 1000)
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => {
console.log('p1中的代码开始执行')
reject(new Error('fail'))
}, 3000)
})
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => {
console.log('p2中的代码开始执行');
resolve(p1)
}, 1000)
})
p2
.then(result => console.log(result))
.catch(error => console.log(error))
分析:
(1) p2的状态在 1s 后改变为成功,resolve(p1)的参数p1还是一个promise,导致p2的状态失效
(2) p2的状态失效,则p2的状态由p1决定
(3) p1的状体在 3s 后改变为失败, reject(new Error('fail')),所以在 3s 后,p2的状态也变成了失败
(4) p2的失败状态由 p2.catch()捕获
- then()
- 返回一个新的promise实例,因此可以采用链式写法
- 如果then()方法链式调用,上一个then()可能返回的是一个promise实例,则后一个then()的回调函数需要等待前一个then()状态改变后才会调用
- catch()
- 返回一个新的promise实例,主要作用是捕获promise过程中的错误
- catch()方法返回的是一个promise对象,因此.catch()后面可以继续调用.then()
- catch()是
.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数 - 只要是catch()前面的then()和promise内部抛出的错误都能被catch()捕获
- 注意:
- 一个在then()中只需要添加成功状态后的回调,而失败的状态由catch()来负责捕获
- finally()
- 不管promise对象最后是什么状态,都会执行的操作
- finally()的回调函数不接受任何参数,因为finally()的回调函数中的操作和状态无关
- all()
- 所有resolve()则fulfilled,一个reject()则rejected
- any()
- 一个resolve()则fulfilled,所有reject()则rejected
- 和all()相反
- race()
- 一个resolve()则fulfilled,一个reject()则rejected
- allSettled()
- 在所有参数的实例都返回结果时,包装实例才会结束
- 比如等待所有请求完成,无论成功失败,都结束loading动画
手写Promise
class Promise {
constructor(executor) {
// 参数不是函数,报错
if (typeof executor !== 'function') {
throw new TypeError(`Promise resolver ${executor} is not a function`)
}
this.init() // 初始化值
try {
executor(this.resolve, this.reject)
} catch (err) {
// 使用 try...catch 的目的是为了把executor()中的错误抛出给then的回调去捕获
this.reject(err)
}
}
init = () => {
this.value = null // 终值
this.reason = null // 拒因
this.state = Promise.PENDING // 状态
this.onFulfilledCallbacks = [] // 成功回调, 在then()方法中push,resolve()时执行
this.onRejectedCallbacks = [] // 失败回调,在then()方法中push,reject()时执行
}
resolve = (value) => {
// 成功后的一系列操作 (状态的改变,成功回调的执行 )
// 状态的改变:pending -> fulfilled
// console.log(this.constructor === Promise) // true
// this 在箭头函数中,作用域绑定在父级执行上下文,即定义时所在的对象
// 即this相当于父级的this,这里又是在勾走函数中,所以this指向了实例对象
if (this.state === Promise.PENDING) {
this.state = Promise.FULFILLED
this.value = value
this.onFulfilledCallbacks.forEach(fn => fn(this.value))
// 当promise的参数函数中有异步操作时,then方法会优先于resolve()或者reject()先执行
// 这样就是导致执行then()方法时,状态是pending状态,因为状态的改变是在resolve()或reject()中改变的,而他们因为异步都没执行
// 这是需要用一个数组来存储将来才会执行的onFulfilled函数
// 这里push进onFulfilledCallbacks的函数,将在resolve()函数中去执行
}
}
reject = (reason) => {
// 失败后的一系列操作 (状态的改变,失败回调的执行 )
// 状态的改变:pending -> rejected
if (this.state === Promise.PENDING) {
this.state = Promise.REJECTED
this.reason = reason
this.onRejectedCallbacks.forEach(fn => fn(this.reason))
}
}
then = (onFulfilled, onRejected) => {
// 参数校验,穿透效果,即then不传任何参数具有穿透效果
if (typeof onFulfilled !== 'function') {
onFulfilled = value => value
}
// 参数校验,穿透效果,即then不传任何参数具有穿透效果
if (typeof onRejected !== 'function') {
onRejected = reason => {
throw reason
}
}
// then()方法返回的是一个新的 promse 实例
// 因为返回新的promise实例,可以可以实现链式调用
let promise2 = new Promise((resolve2, reject2) => {
// 执行onFulfilled函数的条件
if (this.state === Promise.FULFILLED) {
setTimeout(() => {
// 这里中间的then()方法中的回调 onFulfilled() 函数是有返回值的
// 中间then()参数函数onFulfilled()的返回值,会被当做下一个then回调的参数传入
try {
const x = onFulfilled(this.value)
Promise.resolvePromise(promise2, x, resolve2, reject2)
} catch (err) {
reject2(err)
}
})
}
if (this.state === Promise.REJECTED) {
setTimeout(() => {
try {
const x = onRejected(this.reason)
Promise.resolvePromise(promise2, x, resolve2, reject2)
} catch (err) {
reject2(err)
}
})
}
if (this.state === Promise.PENDING) {
// 如果状态是 pending
// 当promise的参数函数中有异步操作时,then方法会优先于resolve()或者reject()先执行
// 这样就是导致执行then()方法时,状态是pending状态,因为状态的改变是在resolve()或reject()中改变的,而他们因为异步都没执行
// 这时需要用一个数组来存储将来才会执行的onFulfilled函数
// 这里push进onFulfilledCallbacks的函数,将在resolve()函数中去执行
this.onFulfilledCallbacks.push((value) => {
// 这里仍然需要使用setTimeout,因为这个函数是在resolve()中执行的,如果resolve()后面任然后同步代码,要保证同步代码先执行
setTimeout(() => {
try {
const x = onFulfilled(value)
Promise.resolvePromise(promise2, x, resolve2, reject2)
} catch (err) {
reject2(err)
}
})
})
this.onRejectedCallbacks.push((reason) => {
setTimeout(() => {
try {
const x = onRejected(reason)
Promise.resolvePromise(promise2, x, resolve2, reject2)
} catch (err) {
reject2(err)
}
})
})
}
})
return promise2
}
}
// 这里使用静态属性,是为了避免 魔法字符串
Promise.PENDING = 'pending'
Promise.FULFILLED = 'fulfilled'
Promise.REJECTED = 'rejected'
Promise.resolvePromise = function (promise2, x, resolve, reject) {
// x 与 promise2 相等
if (promise2 === x) {
reject(new TypeError('chainning cycle detected for promise'))
}
// x 是 Promise
if ( x instanceof Promise) {
x.then(value => {
// resolve(value)
Promise.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)
}
}
const promise = new Promise((resolve, reject) => {
// throw new Error('出错了')
console.log(1)
setTimeout(() => {
console.log(4)
resolve(6)
console.log(5)
})
console.log(2)
})
.then(
value => {
console.log(value, 'value')
return new Promise(resolve => {
resolve(new Promise(resolve3 => {
resolve3(7)
}))
})
},
reason => {
console.log(reason, 'reason')
})
.then(
value => {
console.log(value, 'vvvvvvvvvvvv')
}, reason => {
console.log(reason)
})
console.log(3)
// then
// then中的两个参数回调函数需要异步,setTimeout解决
// 如果promise参数函数内部抛出错误,需要在then()中捕获 => try ... catch
// 如果promise中存在异步,then的回调不会执行 => 因为在执行then方法的时,state === 'pending' 不满足执行then两个回调的任何一个,而当setTimeout中的 resolve() 执行的时,then执行过了就不会再继续执行
new Promise()的参数必须是函数,非函数时会报错
原生:
new Promise(1)
TypeError: Promise resolver 1 is not a function
模拟实现:
class Promise {
constructor(executor) {
if (typeof executor !== 'function') {
// 参数必须是函数,不是函数抛出错误
throw new TypeError(`Promise resolver ${executor} is not a function`)
}
executor(this.resolve, this.reject)
}
}
const promise = new Promise()
// TypeError: Promise resolver undefined is not a function
resolve()方法的主要作用 ( 一共3个 )
- (1) 把状态 ( status ) 从 ( pending -> fulfilled )
- (2) 把终值 ( value ) 赋值为 resolve()函数传入的 ( 参数 )
- (3) 把 ( onFulfilledCallback数组 ) 中的函数,依此取出执行
- 如果promise中存在异步操作时,then()比resolve()先执行
- 所以在then()方法中,需要向onFulfilledCallback数组中push进一个将来在resolve()中状态变成fulfilled后,才会执行的函数
rejected()方法的主要作用 ( 一共3个 )
同时:then() 方法中要做 3种状态的判断- (1) 把状态 ( status ) 从 ( pending -> rejected )
- (2) 把拒因 ( value ) 赋值为 reject()函数传入的 ( 参数 )
- (3) 把 ( onRejectedCallback数组 ) 中的函数,依此取出执行
- 如果promise中存在异步操作时,then()比resolve()先执行
- 所以在then()方法中,需要向onFulfilledCallback数组中push进一个将来在reject()中,状态变成rejected后,才会执行的函数
class Promise {
constructor(executor) {
if (typeof executor !== 'function') {
// 参数必须是函数,不是函数抛出错误
throw new TypeError(`Promise resolver ${executor} is not a function`)
}
this.init()
executor(this.resolve, this.reject)
}
init = () => {
this.value = null // 终值,初始化
this.reason = null // 拒因,初始化
this.status = Promise.PENDING // 状态,初始化时pending
this.onFulfilledCallbacks = [] // 成功回调, 在then()方法中push,resolve()时执行
this.onRejectedCallbacks = [] // 失败回调,在then()方法中push,reject()时执行
}
resolve = (value) => {
if (this.status === Promise.PENDING) {
this.status = Promise.FULFILLED
this.value = value
this.onFulfilledCallbacks.forEach(fn => fn(value))
}
}
reject = (reason) => {
if (this.status === Promise.PENDING) {
this.status === Promise.REJECTED
this.reason = reason
this.onRejectedCallbacks.forEach(fn => fn(resaon))
}
}
// 当promise中存在异步时,then会优先于resolve执行,所以需要有以下判断和策略
// 注意点:当promise的状态是pending状态时,是不知道具体处于哪个阶段的,所以两个数组都要push值
then = (onFulfilled, onRejected) => {
if (this.status === Promise.FULFILLED) {
onFulfilled(this.value)
}
if (this.status === Promise.REJECTED) {
onRejected(this.reason)
}
if (this.status === Promise.PENDING) {
this.onFulfilledCallbacks.push((value) => onFulfilled(value))
this.onRejectedCallbacks.push((reason) => onRejected(reason))
}
}
}
Promise.PENDING = 'pending'
Promise.FULFILLED = 'fulfilled'
Promise.REJECTED = 'rejected'
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
})
}).then(value => {
console.log(value)
}, reason => {
console.log(reason)
})
then() - ( 判断3种状态 )- 2021.12.31更新
- then接受两个函数作为参数
- 第一个函数onfulfilled,在状态变成fulfilled时执行,参数是value终值
- 第二个函数onRejected,在状态变成rejected时执行,参数是reason拒因
- 状态
- fulfilled ---> 执行onfulfilled
- rejected --> 执行onRejected
pending-> 当promsie中有异步操作时,then先于resolve执行,而此时状态是pending,因为resolve还未执行,执行后才会去修改状态,所以为了保证then在状态改变后才去执行,就需要在pending状态时,将onRejected再包装一层,push进一个数组onFulfilledcallbacks中去,在resolve中状态改变后在取出来调用,所以说resolve函数一个有三个作用,1改变状态,2存储终值,3执行then中的回调- -> 结合:
then()保证执行顺序2来一起看 - -> 注意:
- (1)这里只是解决了promise中有异步的情况时候,then不执行的问题
- (2)还没有考虑异步中同时有同步任务的情况,执行顺序的问题,当promise中有异步任务,而异步任务中又有同步任务时,任然要保证then的执行在异步任务中的同步任务执行之后再执行
- 请结合
then()保证执行顺序3来一起看
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)
}
}
then()保证执行顺序1
- then()需要在同步代码执行完后,then()中的回调函数才能执行
console.log(1)
const promise = new Promise((resolve, reject) => {
console.log(2)
resolve(5)
console.log(3)
})
.then(value => {
console.log(value)
}, reason => {
console.log(reason)
})
console.log(4)
问题:如何保证执行顺序是 12345
解析:4是同步任务,而then是微任务,要保证4先执行,5后执行
解决:then()是异步方法,即then()方法的参数回调需要在resolve()或reject()方法执行后才执行,用 ( 定时器 ) 解决
说明:如果不用定时器执行顺序是 12354
then = (onFulfilled, onRejected) => {
if (typeof onFulfilled !== Promise.FUNCTION) {
onFulfilled = value => value
}
if (typeof onRejected !== Promise.FUNCTION) {
onRejected = reason => reason
}
if (this.status === Promise.FULFILLED) {
setTimeout(() => { // 用setTimeout()来模拟异步执行onFulfilled,保证同步代码执行后再执行onFulfilled
onFulfilled(this.value)
})
}
if (this.status === Promise.REJECTED) {
setTimeout(() => { // 用setTimeout()来模拟异步执行onRejected,保证同步代码执行后再执行onRejected
onRejected(this.reason)
})
}
if (this.status === Promise.PENDING) {
this.onFulfilledCallbacks.push((value) => onFulfilled(value))
this.onRejectedCallbacks.push((reason) => onRejected(reason))
}
}
then()保证执行顺序2
- 当promise中有有异步代码时,then()方法会比resolve()先执行,此时statuss='pending'状态
- 而在then方法中并未添加状态是pending状态时的相关操作时,then()中的两个回调都不会执行
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数组
// 2021.12.31更新一个问题
// 问题:这里为什么要把onFulfilled在外面再包装一层函数
// 回答:因为这里要是保证 onFulfilled 可以在调用时传参,在resolve中调用时,传入终值,然后在传给 onFulfilled ---- 因为: resolve() 未执行时,是不知道终值value是多少的
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))
}
}
then()保证执行顺序3
console.log(1)
new Promise((resolve, reject) => {
console.log(2)
setTimeout(() => {
resolve()
console.log(4) // 要保证4比5先执行,因为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() 的链式调用
- then()方法返回的是新的promise实例,注意不是原来的,所以可以链式调用
- 新的promise中参数函数的resolve的是onFufiled函数执行后返回的值
then = (onFulfilled, onRejected) => {
// 参数校验,穿透效果,即then不传任何参数具有穿透效果
if (typeof onFulfilled !== Promise.FUNCTION) {
onFulfilled = value => value
}
// 参数校验,穿透效果,即then不传任何参数具有穿透效果
if (typeof onRejected !== Promise.FUNCTION) {
onRejected = reason => reason
}
const promise2 = new Promise((resolve2, reject2) => {
if (this.status === Promise.FULFILLED) {
setTimeout(() => {
const x = onFulfilled(this.value)
// 将onFulfilled函数的返回值作为resolve()的参数,传给新的 then() 方法
resolve2(x)
})
}
if (this.status === Promise.REJECTED) {
setTimeout(() => {
const x = onRejected(this.reason)
reject2(x)
})
}
if (this.status === Promise.PENDING) {
this.onFulfilledCallbacks.push((value) => {
setTimeout(() => {
const x = onFulfilled(value)
resolve2(x)
})
})
this.onRejectedCallbacks.push((reason) => {
setTimeout(() => {
const x = onRejected(reason)
reject2(x)
})
})
}
})
return promise2
}
更详细
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()模拟实现
p = Promsie.all([p1, p2, p3])- 返回值:Promise.all() 返回一个新的promise实例
- 参数:
- ( 参数 ) 是一个 ( 数组 ) 或者是具有 ( Iterator ) 接口类型的数据,成员都是promsie实例
- 如果不是promsie实例就会调用Promsie.resolve()转成promsie实例
- 作用:
- 如果所有参数数组成员的状态,都变成了fulfilled,则整个状态变成fulfilled
- 此时 p1,p2,p3中resolve()的返回值,将组成一个数组,传递给p的回调函数
- 如果有一个参数数组成员的状态变成了rejected,则整个状态变成了rejected
- 此时,第一个被拒绝的reject()的返回值,会传递给p的回调函数
- 如果所有参数数组成员的状态,都变成了fulfilled,则整个状态变成fulfilled
说明:
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个数
// 其实这里可以不要count,直接通过 arr 的长度来进行判断,具体请看2021-10-24的更新
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'))
2021/4/7修改 Promise.all 模拟实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 手写 Promise.all
// 1. Promise.all返回的是一个新的promise
// 2. Promise.all的参数是一个数组,成员是promise对象,如果不是promise对象会先把参数转成promise
// 3. 所有成员fulfilled则整个状态变成 fulfilled
// 4. 一个rejected则整个状态变成 rejected
const promise1 = Promise.resolve(1)
const promise2 = Promise.resolve(2)
const promise3 = Promise.resolve(3)
const promise4 = Promise.reject(new Error('出错了'))
Promise.all2 = (promises) => {
return new Promise((resolve, reject) => { // Promise.all()返回一个新的promise对象
const promisesArr = Array.from(promises)
const len = promisesArr.length
let count = 0
const results = []
for (let i = 0; i < len; i++) {
Promise.resolve(promisesArr[i]).then(value => { // 如果参数不是promise则先转成promise
count++
results.push(value)
if (results.length === len) {
resolve(results) // 当数组整个遍历完后,都没发生错误的情况下,resolve(results) 整个终值数组
}
}, reason => reject(reason)) // 只要一个产生错误,整个 reject最先发生的拒因
}
})
}
Promise.all2([promise1, promise2, promise3]) // fulfilled
.then(value => console.log(value))
.catch(err => console.log(err))
Promise.all2([promise1, promise2, promise3, promise4]) // rejected
.then(value => console.log(value))
.catch(err => console.log(err))
</script>
</body>
</html>
Proimse.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是成功时的回调函数接收到的终值
})
}
})
}
总结遇到的一些面试题 2021/04/10
(1) promise面试题1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
console.log(1)
setTimeout(() => { // 定时器 A
console.log(2);
Promise.resolve().then(() => { // Promise D
console.log(3)
})
})
new Promise((resolve, reject) => { // Promise B
console.log(4)
resolve(5)
}).then((data) => {
console.log(data)
})
setTimeout(() => { // 定时器 C
console.log(6)
})
console.log(7)
/****
答案:1 4 7 5 2 3 6
分析:
1. 第一次事件循环
同步任务:1
宏任务:[A, C]
微任务:[B]
输出:1 4 7
2. 第二次事件循环
遍历所有微任务[B] => 5
取出第一个宏任务[A] => 2 , 并将微任务D添加进任务队列,此时微任务队列 [D]
输出:5 2
3. 第三次事件循环
遍历所有微任务[D] => 3
取出第一个红任务[C] -> 6
输出:3 6
*/
</script>
</body>
</html>
(2) promise面试题2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
console.log(1)
// A promise
new Promise((resolve) => {
console.log(2)
resolve()
console.log(3)
}).then(res => { // E then
console.log(4)
// C 定时器
setTimeout(() => console.log(5))
})
console.log(6)
// B 定时器
setTimeout(() => {
console.log(7)
// D promise
new Promise((resolve) => {
console.log(8)
resolve()
}).then(() => console.log(9)) // F then
})
console.log(10)
/**
* 第一轮 Event loop
* 1 => 同步任务,进入函数调用栈,立即执行
* A => A的回调立即执行
* 2 => 同步任务,立即执行
* E => 微任务,进入微任务队列
* 3 => 同步任务,立即执行
* 6 => 同步任务,立即执行
* B => 宏任务,B的回调进入宏任务队列
* 10 => 同步任务,立即执行
* 此时执行情况如下:
* 输出:1,2,3,6,10
* 微任务:[E]
* 宏任务:[B]
*
* 第二轮 Event loop
* 清空微任务队列,取出宏任务队列的第一个成员
* E => 4 同步任务,立即执行
* C 宏任务,进入宏任务队列,此时的宏任务队列 [B, C]
* B => 7 同步任务,立即执行
* D promise的回调立即执行
* => 8 同步任务,立即执行
* => F 微任务,进入微任务队列,此时的微任务队列 [F]
* 此时执行情况如下:
* 输出:4,7,8
* 微任务:[F]
* 宏任务:[C]
*
* 第三轮 Event loop
* 清空微任务队列,取出宏任务队列的第一个成员
* F => 9 同步任务,立即执行
* C => 5 同步任务,立即执行
*
* 总的输出顺序:1,2,3,6,10,4,7,8,9,5
*/
</script>
</body>
</html>
2021/10/24 复习总结更新如下
(一) 第一步:实现 new Promise(resolve => resolve(1))
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>手写promise</title>
</head>
<body>
<script>
// 手写promise
// 1
// 第一步实现
// new Promise(resolve => resolve(1))
// 2
// 注意点
// 1. 构造函数:Promise是通过new命名来调用的,说明 ( Promise是一个构造函数 )
// 2. 参数:
// - 报错:当Promise构造函数传入的 ( 参数不是函数时 ) 会报错
// - 具体:new Promise(1) 报错:Promise resolver 1 is not a function
// - 校检:所以不能相信用户的输入,需要做 ( 参数校检 )
class _Promise {
constructor(executor) {
// 参数校检
if (typeof executor !== "function") {
throw new TypeError(
`Promise resolver ${executor} is not a function`
);
}
executor(this.resolve, this.reject);
// 1. executor 执行器函数是外部传入的
// 2. executor 函数的两个参数resolve函数和reject函数,是内部定义的
// 3. 传入的参数函数 executor 将在构造函数中被调用
}
value = null;
reason = null;
state = "pending";
resolve = (value) => {
// resolve 函数
// - 参数:终值value
// - 作用:
// 1.存储终值
// 2.改变promise的状态
if (this.state === "pending") {
this.value = value; // 存储终值
this.state = "fulfilled"; // 改变状态 - 从 'pending' -> 'fulfilled'
}
};
reject = (reason) => {
// reject 函数
// - 参数:拒因reason
// - 作用:
// 1.存储拒因
// 2.改变promise的状态
if (this.state === "pending") {
this.reason = reason; // 存储拒因
this.state = "rejected"; // 改变状态 - 从 'pending' -> 'rejected'
}
};
}
new _Promise((resolve) => {
console.log("start");
resolve(1);
});
// 1. new 的作用是执行构造函数 ( 如果是类,则是执行类中的构造函数constructor ),并返回生成的实例对象
</script>
</body>
</html>
(二) 第二步:实现 then
(1) 需要考虑:- 穿透效果
(2) 需要考虑:- executor 中抛错时,如何捕获?,则需要用try...catch包裹整个executor,有错误则将err传入reject中
(3) 需要考虑:- 执行顺序,then函数的两个回调onFulfilled和onRejected的执行,需要在 ( 同步代码执行完后 )
(4) 需要考虑:- 执行顺序,executor函数中有异步操作时,then和resolve两个函数的执行顺序问题
(5) 需要考虑:- 链式调用,每个then返回新的promise,因此能链式调用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>手写promise</title>
</head>
<body>
<script>
// 手写promise
// 一
// 1
// 第一步实现
// new Promise(resolve => resolve(1))
// 2
// 注意点
// 1. 构造函数:Promise是通过new命名来调用的,说明 ( Promise是一个构造函数 )
// 2. 参数:
// - 报错:当Promise构造函数传入的 ( 参数不是函数时 ) 会报错
// - 具体:new Promise(1) 报错:Promise resolver 1 is not a function
// - 校检:所以不能相信用户的输入,需要做 ( 参数校检 )
// 二
// 第二步实现
// new Promise().then() -> then
// 1
// then方法是挂载在哪里?
// - Promise.prototype 上的,即 --> Promise.prototype.then
// 2
// then方法的作用
// - 为 promise 实例,添加 ( 状态改变 ) 后的 ( 回调函数 )
// 3
// then方法的参数
// - 第一个参数:可选,是一个函数,是promise状态变成fulfilled状态的回调函数
// - 第二个参数:可选,是一个函数,是promise状态变成rejected状态的回调函数
// 4
// then方法的返回值
// - then放回一个新的promise对象,所以可以实现链式调用
// -- 扩展:Promise.prototype上的三个方法都是返回新的promise对象 --> then catch finally
// 5
// then中两个回调的执行时机
// 需要考虑:- executor 中抛错时,如何捕获?,则需要用try...catch包裹整个executor,有错误则将err传入reject中
// 需要考虑:- 执行顺序,then函数的两个回调onFulfilled和onRejected的执行,需要在 ( 同步代码执行完后 )
// 需要考虑:- 执行顺序,executor函数中有异步操作时,then和resolve两个函数的执行顺序问题
// 需要考虑:- 链式调用,每个then返回新的promise,因此能链式调用
class _Promise {
constructor(executor) {
// 参数校检
if (typeof executor !== "function") {
throw new TypeError(
`Promise resolver ${executor} is not a function`
);
}
try {
executor(this.resolve, this.reject);
// 1. executor 执行器函数是外部传入的
// 2. executor 函数的两个参数resolve函数和reject函数,是内部定义的
// 3. 传入的参数函数 executor 将在构造函数中被调用
} catch (err) {
this.reject(err);
}
}
// 属性
value = null;
reason = null;
state = "pending";
onFulfilledCallbacks = []; // 用来存储:executor中存在异步时,存储resolve执行状态改变后,要在then的onFulfilled函数中执行的回调函数
onRejectedCallbacks = [];
resolve = (value) => {
// resolve 函数
// - 参数:终值value
// - 作用:
// 1.存储终值
// 2.改变promise的状态
if (this.state === "pending") {
this.value = value; // 存储终值
this.state = _Promise.FULFILLED; // 改变状态 - 从 'pending' -> 'fulfilled'
this.onFulfilledCallbacks.forEach((fn) => fn(this.value)); // 保证 resolve 修改状态后,在执行then中的 onFulfilled 回调
}
};
reject = (reason) => {
// reject 函数
// - 参数:拒因reason
// - 作用:
// 1.存储拒因
// 2.改变promise的状态
if (this.state === "pending") {
this.reason = reason; // 存储拒因
this.state = _Promise.REJECTED; // 改变状态 - 从 'pending' -> 'rejected'
this.onRejectedCallbacks.forEach((fn) => fn(this.reason)); // 保证 rejected 修改状态后,在执行then中的 onRejected 回调
}
};
then = (onFulfilled, onRejected) => {
// 参数
// - onFulfilled 和 onRejected 都是外面传入的函数,分别是promise实例成功状态和失败状态的回调函数
// 调用
// - then 方法是在promise实例上调用的,所以then这里是原型属性
// 重写 onFulfilled
if (typeof onFulfilled !== "function") {
// onFulfilled 不是一个函数,则重写onFulfilled,直接返回终值
// 案例:Promise.resolve(1).then().then(value => console.log(value))
// --> value 是 1
// --> 即第一个then()没有传入任何参数,第二个then的第一个回调的参数还是1
// --> 即 ( 穿透效果 )
onFulfilled = (value) => value;
}
// 重写 onRejected
if (typeof onRejected !== "function") {
// onRejected 不是一个函数,则重写onFulfilled,直接返回拒因
onRejected = (reason) => reason;
}
// 执行 onFulfilled
if (this.state === _Promise.FULFILLED) {
// 状态:只有在promise的状态是fulfilled的时候,才能执行 onFulfilled 回调函数
// 参数:onFulfilled 回调的参数是:终值value
// 魔法字符串如下:
// --> his.state === "fulfilled"
// --> 统一用常量来代替
// --> this.state === _Promise.FULFILLED
setTimeout(() => {
// 用setTimeout来保证onFulfilled的执行,在同步代码执行完之后
onFulfilled(this.value);
});
}
// 执行 onRejected
if (this.state === _Promise.REJECTED) {
// 状态:只有在promise的状态是rejected的时候,才能执行 onRejected 回调函数
// 参数:onRejected 回调的参数是:拒因reason
setTimeout(() => onRejected(this.reason));
}
// pending状态下的处理,即executor中有异步的情况
if (this.state === _Promise.PENDING) {
this.onFulfilledCallbacks.push((value) => {
// 1. push的是一个函数,之所以要报包装成函数,是因为onFulfilled还要用setTimeout包裹
// 2. 调用时push的函数时,传入的参数是 this.value;调用时机是在executor的resolve函数中
setTimeout(() => onFulfilled(value));
});
this.onRejectedCallbacks.push((reason) =>
setTimeout(() => onRejected(reason))
);
}
};
}
_Promise.PENDING = "pending";
_Promise.FULFILLED = "fulfilled";
_Promise.REJECTED = "rejected";
// 执行顺序
console.log(1111111);
new _Promise((resolve) => {
console.log("start");
setTimeout(() => {
console.log(2222222);
resolve(1);
});
}).then(
(value) => {
console.log(`value`, value);
console.log(4444444);
},
(reason) => console.log(`reason`, reason)
);
console.log(3333333);
// 1. new 的作用是执行构造函数 ( 如果是类,则是执行类中的构造函数constructor ),并返回生成的实例对象
// 2. 要保证执行顺序是:1111111 2222222 3333333 4444444
// ---- 2.1
// ---- 即:要保证 then 中的两个回调,要在同步任务执行完后,才能执行
// ---- 所以:then方法中的 onFulfilled 和 onRejected 的执行时机,需要要setTimeout来模拟
// ---- 效果:未用setTimeout包装onFulfilled 和 onRejected前,执行顺序1243,包装后1234
// ---- 2.2
// ---- 异步:如果 executor 中存在异步函数时,resolve 和 then 的执行顺序问题
// ---- 遇到问题:
// ------- 问题:如果在executor中,用setTimeout包裹了resolve时,then方法执行时,状态是pending
// ------- 原因:( 因为异步包装后resolve还未执行,只有resolve执行后,状态才会变成fulfilled,而then的onFulfilled函数需要在fulfilled状态才会执行 )
// ------- 本质:因为 then 先于 resolve 执行了,导致状态pending时,then第一个参数-成功回调onFulfilled不会执行,所以 4444444 不会输出
// ------- 解决办法:利用数组存储 resolve 执行,改变了promise状态为 fulfilled 后的,在 then 中需要执行的 onFulfilled 包装后的函数
</script>
</body>
</html>
(三) 实现 then - 链式调用
- 因为:then方法返回一个新的promise,即可以链式调用
- 所以:我们在then方法中,需要 new _Promise2() ,并返回生成的实例promise2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>手写promise</title>
</head>
<body>
<script>
// 手写promise
// 一
// 1
// 第一步实现
// new Promise(resolve => resolve(1))
// 2
// 注意点
// 1. 构造函数:Promise是通过new命名来调用的,说明 ( Promise是一个构造函数 )
// 2. 参数:
// - 报错:当Promise构造函数传入的 ( 参数不是函数时 ) 会报错
// - 具体:new Promise(1) 报错:Promise resolver 1 is not a function
// - 校检:所以不能相信用户的输入,需要做 ( 参数校检 )
// 二
// 第二步实现
// new Promise().then() -> then
// 1
// then方法是挂载在哪里?
// - Promise.prototype 上的,即 --> Promise.prototype.then
// 2
// then方法的作用
// - 为 promise 实例,添加 ( 状态改变 ) 后的 ( 回调函数 )
// 3
// then方法的参数
// - 第一个参数:可选,是一个函数,是promise状态变成fulfilled状态的回调函数
// - 第二个参数:可选,是一个函数,是promise状态变成rejected状态的回调函数
// 4
// then方法的返回值
// - then放回一个新的promise对象,所以可以实现链式调用
// -- 扩展:Promise.prototype上的三个方法都是返回新的promise对象 --> then catch finally
// 5
// then中两个回调的执行时机
// 需要考虑:- executor 中抛错时,如何捕获?,则需要用try...catch包裹整个executor,有错误则将err传入reject中
// 需要考虑:- 执行顺序,then函数的两个回调onFulfilled和onRejected的执行,需要在 ( 同步代码执行完后 )
// 需要考虑:- 执行顺序,executor函数中有异步操作时,then和resolve两个函数的执行顺序问题
// 需要考虑:- 链式调用,每个then返回新的promise,因此能链式调用
class _Promise {
constructor(executor) {
// 参数校检
if (typeof executor !== "function") {
throw new TypeError(
`Promise resolver ${executor} is not a function`
);
}
try {
executor(this.resolve, this.reject);
// 1. executor 执行器函数是外部传入的
// 2. executor 函数的两个参数resolve函数和reject函数,是内部定义的
// 3. 传入的参数函数 executor 将在构造函数中被调用
} catch (err) {
this.reject(err);
}
}
// 属性
value = null;
reason = null;
state = "pending";
onFulfilledCallbacks = []; // 用来存储:executor中存在异步时,存储resolve执行状态改变后,要在then的onFulfilled函数中执行的回调函数
onRejectedCallbacks = [];
resolve = (value) => {
// resolve 函数
// - 参数:终值value
// - 作用:
// 1.存储终值
// 2.改变promise的状态
if (this.state === "pending") {
this.value = value; // 存储终值
this.state = _Promise.FULFILLED; // 改变状态 - 从 'pending' -> 'fulfilled'
this.onFulfilledCallbacks.forEach((fn) => fn(this.value)); // 保证 resolve 修改状态后,在执行then中的 onFulfilled 回调
}
};
reject = (reason) => {
// reject 函数
// - 参数:拒因reason
// - 作用:
// 1.存储拒因
// 2.改变promise的状态
if (this.state === "pending") {
this.reason = reason; // 存储拒因
this.state = _Promise.REJECTED; // 改变状态 - 从 'pending' -> 'rejected'
this.onRejectedCallbacks.forEach((fn) => fn(this.reason)); // 保证 rejected 修改状态后,在执行then中的 onRejected 回调
}
};
then = (onFulfilled, onRejected) => {
// 参数
// - onFulfilled 和 onRejected 都是外面传入的函数,分别是promise实例成功状态和失败状态的回调函数
// 调用
// - then 方法是在promise实例上调用的,所以then这里是原型属性
// 重写 onFulfilled
if (typeof onFulfilled !== "function") {
// onFulfilled 不是一个函数,则重写onFulfilled,直接返回终值
// 案例:Promise.resolve(1).then().then(value => console.log(value))
// --> value 是 1
// --> 即第一个then()没有传入任何参数,第二个then的第一个回调的参数还是1
// --> 即 ( 穿透效果 )
onFulfilled = (value) => value;
}
// 重写 onRejected
if (typeof onRejected !== "function") {
// onRejected 不是一个函数,则重写onFulfilled,直接返回拒因
onRejected = (reason) => reason;
}
// ------------------------------------------------------------------------------------------------------------ 链式调用 start
// 链式调用
// - then返回一个新的promise对象
// - 实现链式调用,并且改变了后面then的值,必须通过新的实例
const promise2 = new _Promise((resolve, reject) => {
// 执行 onFulfilled
if (this.state === _Promise.FULFILLED) {
// 状态:只有在promise的状态是fulfilled的时候,才能执行 onFulfilled 回调函数
// 参数:onFulfilled 回调的参数是:终值value
// 魔法字符串如下:
// --> his.state === "fulfilled"
// --> 统一用常量来代替
// --> this.state === _Promise.FULFILLED
setTimeout(() => {
// 用setTimeout来保证onFulfilled的执行,在同步代码执行完之后
try {
const x = onFulfilled(this.value);
resolve(x);
} catch (err) {
reject(err);
}
});
}
// 执行 onRejected
if (this.state === _Promise.REJECTED) {
// 状态:只有在promise的状态是rejected的时候,才能执行 onRejected 回调函数
// 参数:onRejected 回调的参数是:拒因reason
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolve(x);
} catch (err) {
reject(err);
}
});
}
// pending状态下的处理,即executor中有异步的情况
if (this.state === _Promise.PENDING) {
this.onFulfilledCallbacks.push((value) => {
// 1. push的是一个函数,之所以要报包装成函数,是因为onFulfilled还要用setTimeout包裹
// 2. 调用时push的函数时,传入的参数是 this.value;调用时机是在executor的resolve函数中
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolve(x);
} catch (err) {
reject(err);
}
});
});
this.onRejectedCallbacks.push((reason) => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolve(x);
} catch (err) {
reject(err);
}
});
});
}
});
return promise2;
// ------------------------------------------------------------------------------------------------------------ 链式调用 end
};
}
_Promise.PENDING = "pending";
_Promise.FULFILLED = "fulfilled";
_Promise.REJECTED = "rejected";
_Promise.resolvePromise = (promise2, x, resolve, reject) => {};
// 执行顺序
console.log(1111111);
new _Promise((resolve) => {
console.log("start");
setTimeout(() => {
console.log(2222222);
resolve(1);
});
})
.then(
(value) => {
console.log(`value`, value);
console.log(4444444);
return value;
// throw new Error("出错了");
},
(reason) => {
console.log(`reason`, reason);
return reason;
}
)
.then(
(value) => {
console.log(`value`, value);
console.log(5555555);
return value;
},
(reason) => console.log("reason", reason)
);
console.log(3333333);
// 1. new 的作用是执行构造函数 ( 如果是类,则是执行类中的构造函数constructor ),并返回生成的实例对象
// 2. 要保证执行顺序是:1111111 2222222 3333333 4444444
// ---- 2.1
// ---- 即:要保证 then 中的两个回调,要在同步任务执行完后,才能执行
// ---- 所以:then方法中的 onFulfilled 和 onRejected 的执行时机,需要要setTimeout来模拟
// ---- 效果:未用setTimeout包装onFulfilled 和 onRejected前,执行顺序1243,包装后1234
// ---- 2.2
// ---- 异步:如果 executor 中存在异步函数时,resolve 和 then 的执行顺序问题
// ---- 遇到问题:
// ------- 问题:如果在executor中,用setTimeout包裹了resolve时,then方法执行时,状态是pending
// ------- 原因:( 因为异步包装后resolve还未执行,只有resolve执行后,状态才会变成fulfilled,而then的onFulfilled函数需要在fulfilled状态才会执行 )
// ------- 本质:因为 then 先于 resolve 执行了,导致状态pending时,then第一个参数-成功回调onFulfilled不会执行,所以 4444444 不会输出
// ------- 解决办法:利用数组存储 resolve 执行,改变了promise状态为 fulfilled 后的,在 then 中需要执行的 onFulfilled 包装后的函数
</script>
</body>
</html>
(四) 实现 Promise.all
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
// Promise.all 的模拟实现
// 手写
// (一)
// Promise.all
// 1
// 参数
// - 数组:
// --- 数组成员是promise:参数是数组的话,每个成员都要是promise实例对象
// --- 数组成员不是promise:如果成员不是则会先调用 Promise.resolve() 将成员转成 promise对象
// 返回值
// - 返回一个新的promise对象,所以可以在all后调用then
// (二)
// Promise.resolve(params)
// - params是promise对象 =====================================> 直接返回该promise对象
// - params是thenable对象 ===> thenable对象指具有then方法的对象 => 会先转成promise对象,再执行对象中的then方法
// - 参数是原始值 =============================================> 返回一个新的promise对象,状态是resolve状态
// - 不带任何参数 =============================================> 直接返回一个 fulfilled 状态的 promise 对象
const p1 = Promise.resolve("p1");
const p2 = Promise.resolve("p2");
const p3 = Promise.resolve("p3");
const str4 = "string4";
// 手写
Promise.all2 = function (promiseList) {
return new Promise((resolve, reject) => {
const result = [];
const promiseArr = Array.from(promiseList); // promiseList可能是一个数组,或者是具有Iterator接口的数据 => 所以这里都先处理成数组
const len = promiseArr.length;
promiseArr.forEach((_promise) => {
// 如果参数数组成员不是promise对象,则会先调用Promise.resolve()将其转换成promise对象
Promise.resolve(_promise).then(
(promise) => {
result.push(promise); // 将真正的promise对象的,fulfilled状态下的终值添加进数组
if (result.length === len) {
resolve(result); // 当所有参数成员的状态都是fulfilled时,整个状态变成fulfilled
}
},
reject
// reject
// - Promise.resolve(_promise).then(value => {}, reason => {})
// - reject 就是上面的 reason => {}
// - 表示:如果每个promise的状态任何一个如果是reject时,就把整个要返回的promise的对象的状态变成rejected,即调用reject方法
// - 注意点:这里的 reject 是把 ( 整个新的promise的resolve ) 赋值给了 (Promise.resolve(_promise).then()的第二个参数 )
);
});
});
};
// 原生
Promise.all([p1, p2, p3, str4]).then((resArr) => {
console.log(`resArr`, resArr);
});
// 手写
Promise.all2([p1, p2, p3, str4]).then((resArr) => {
console.log(`resArr`, resArr);
});
</script>
</body>
</html>
(五) 实现 Promise.race
- 知道了 Promise.all 那么 Promise.race 就很简单了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
const promise1 = new Promise(
(resolve) => setTimeout(() => resolve(1)),
1000
);
const promise2 = new Promise(
(resolve) => setTimeout(() => resolve(2)),
2000
);
const promise3 = new Promise(
(resolve, reject) => setTimeout(() => reject(2)),
3000
);
// Promise.race
// 1. 返回:一个新的 promise 对象
// 2. 参数:是 数组 或 Iterator 接口数据类型、
// 3. 参数成员:参数数组 成员 不是promise对象,会被 Promise.resolve() 转成 promise 对象
// 4. 效果:谁先fulfilled整个fulfilled,谁先rejected整个rejected
Promise._race = function (params) {
return new Promise((resolve, reject) => {
const paramsArr = Array.from(params);
paramsArr.forEach((_promise) => {
Promise.resolve(_promise).then(
(value) => resolve(value),
(reason) => reject(reason)
);
});
});
};
Promise.race([promise1, promise2, promise3]).then(
(values) => console.log("values", values),
(reason) => console.log("reason", reason)
);
Promise._race([promise1, promise2, promise3]).then(
(values) => console.log("_values", values),
(reason) => console.log("_reason", reason)
);
</script>
</body>
</html>
代码仓库
- 代码仓库: github.com/woow-wu7/6-…
资料
手写promise segmentfault.com/a/119000001…
手写promise juejin.im/post/684490…