初始化项目
1.生成package.json
运行命令,生成package.json文件
npm init -y
2.安装rollup,配置rollup.config.js
import commonjs from 'rollup-plugin-commonjs'
export default {
input:'./src/core.js',
output:{
file:'./dist/index.js',
format:'umd',
name:'myPromise'
},
plugins:[
commonjs()
]
}
package.json添加命令:
"build": "rollup -c -w",
3.初始目录结构
Promise的状态和then方法
性质
- 当使用Promise的时候会传入一个执行器,这个执行器会立即执行,而且可能会出错,需要用try...catch捕获这个错误
- executor函数接受两个函数,resolve和reject来改变promise的状态
- resolve和reject接受参数来改变promise的值(既改变状态也改变值)
- Promise的then方法是异步的
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class myPromise {
constructor(executor) {
this.status = PENDING // promise默认的状态
this.value = undefined // 成功的原因
this.reason = undefined // 失败的原因
const resolve = (value) => {
if (this.status === PENDING) { // 只有在PENDING状态时才改变状态和值
this.value = value
this.status = FULFILLED
}
}
const reject = (reason) => {
if (this.status === PENDING) { // 只有在PENDING状态时才改变状态和值
this.reason = reason
this.status = REJECTED
}
}
try {
executor(resolve, reject)
} catch (e) {
reject(e) // 一旦抛出异常 走reject
}
}
then(onFulFilled,onRejected) { // 成功的回调 和 失败的回调
if(this.status === FULFILLED){
onFulFilled(this.value) // 把成功的值传给成功回调
}
if(this.status === REJECTED){
onRejected(this.reason) // 把失败的原因传给失败的回调
}
}
}
module.exports = myPromise
DEMO
let a = new myPromise((resolve, reject) => {
console.log('gsdmypromise')
resolve('gsda') // 走resolve改变了状态之后不会再走reject
reject('gsdb')
}).then(result => {
console.log('gsdresult', result) // 这里的result就是上面resolve里的参数gsda
}, reason => {
console.log('gsdreason', reason)
})
【注意】executor接受的resolve和reject只是用来更改primise实例的状态和值,而then方法接受的两个参数是回调函数,根据promise实例状态的不同调用不同的回调函数
存储成功或者失败的回调
当用户调用then方法的时候,promise实例状态可能还是为pending,需要把成功回调或者失败回调暂存起来,等resolve或者reject改变实例状态后,在触发对应的成功或者失败回调
例如下面例子:
let a = new myPromise((resolve, reject) => { // 此时状态为PENDING
setTimeout(() => {
resolve('gsda')
}, 1000);
}).then(result => { // 此时走then方法时,promise状态为PENDING,1秒后才修改为FULFILLED
console.log('gsdresult', result)
}, reason => {
console.log('gsdreason', reason)
})
console.log('then方法返回的promise',a)
因此对前面的源码进行优化
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class myPromise {
constructor(executor) {
this.status = PENDING
this.value = undefined
this.reason = undefined
this.onResolvedCallbacks = [] // 存放成功的回调
this.onRejectedCallbacks = [] // 存放失败的回调
const resolve = (value) => {
if (this.status === PENDING) {
this.value = value
this.status = FULFILLED
this.onResolvedCallbacks.forEach(fn=>fn()) // 发布模式
}
}
const 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) {
if(this.status === FULFILLED){
onFulFilled(this.value)
}
if(this.status === REJECTED){
onRejected(this.reason)
}
if(this.status === PENDING){ // 代码是异步调用resolve或者reject 订阅模式
// 不直接push onRejected(this.reason)或 onFulFilled(this.value)目的是可以加入自己的逻辑
this.onRejectedCallbacks.push(()=>{
// do something
onRejected(this.reason)
})
this.onResolvedCallbacks.push(()=>{
// do something
onFulFilled(this.value)
})
}
}
}
module.exports = myPromise
链式调用
promise核心
-
用链式调用解决了回调地域问题
-
同步并发问题,有多个并发的请求,用promise来同步它们的结果
-
多个异步处理错误问题,没有promise之前每个回调函数都要单独处理错误
promise的then方法返回值处理
当调用then方法后会返回一个新的promise
// 原生Promise链式调用特点
let promise = new Promise((resolve,reject)=>{
resolve('hello')
})
// 情况1:通过return方式传递结果,传递普通值,可以传递到下一个then方法里的成功回调函数
promise.then((res)=>{
return res
})
.then((res)=>{
console.log(res) // hello
})
// 情况2:通过新的Promise resovle结果,传递到下一个then方法的成功回调
promise.then((res)=>{
return res
})
.then((res)=>{
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(res)
},2000)
})
.then((res)=>{
console.log(res) // hello
})
// 情况3:通过新的Promise reject结果,传递到下一个then方法的失败回调
promise.then((res)=>{
return res
})
.then((res)=>{
return new Promise((resolve,reject)=>{
setTimeout(()=>{
reject('error')
},2000)
})
.then((res)=>{
console.log(res)
},(err)=>{
console.log(err) // error
})
// 情况4:then走了失败的回调,再调用then,走成功的回调,但是值为undefined
promise.then((res)=>{
return res
})
.then((res)=>{
return new Promise((resolve,reject)=>{
setTimeout(()=>{
reject('error')
},2000)
})
.then((res)=>{
console.log(res)
},(err)=>{
console.log(err) // error 默认return undefined
})
.then((res)=>{
console.log(res) //undefined
})
// 情况5:如果then中用throw ,那么下一次then走失败的回调,值为抛出的错误
promise.then((res)=>{
return res
})
.then((res)=>{
return new Promise((resolve,reject)=>{
setTimeout(()=>{
reject('error')
},2000)
})
.then((res)=>{
console.log(res)
},(err)=>{
console.log(err) // error 默认return undefined
})
.then(()=>{
throw new Error('throw error')
})
.then((res)=>{
console.log(res)
},(err)=>{
console.log(err) // throw error
})
// 情况6:catch相当于then,遵循then的运行原则,可以捕获异常
// 如果上一个then里面return失败的条件,那么下一个then走失败的回调,但是如果下一个then没有定义失败的回调,那么走catch
// 如果下一个then定义了失败的回调,那么就不走catch
// catch后面还可以调用then,catch返回的是一个新的promise实例,执行的是then方法的第一个回调函数
总结
-
成功的条件
-
then里面return 普通的值
-
then里面return 新的Promise的成功的结果
-
无论上一次then走成功回调还是失败回调,只要返回值是普通值或者新的Promise的成功结果,下一次then走成功回调
-
失败的条件
-
then里面return 新的Promise的失败的原因
-
then里面抛出异常 throw new Error
-
无论上一次then走成功回调还是失败回调,只要返回值是抛出异常或新的Promise的失败结果,下一次then走失败回调
then返回一个普通值
无论上一个then走失败的回调还是成功的回调,只要它返回一个普通值,那么下次then走成功的回调
如果上一个then抛出错误,下次then走失败的回调
下面的x就是成功回调或者失败回调执行的结果,如果x是一个普通值的时候
then(onFulFilled, onRejected) {
// 每次调用then方法返回一个新的promise 用于实现链式调用
let promise2 = new myPromise((resolve, reject) => {
if (this.status === FULFILLED) {
try {
let x = onFulFilled(this.value)
resolve(x)
} catch (error) {
reject(error)
}
}
if (this.status === REJECTED) {
try {
let x = onRejected(this.reason)
resolve(x)
} catch (error) {
reject(error)
}
}
if (this.status === PENDING) {
this.onRejectedCallbacks.push(() => {
// do something
try {
let x = onRejected(this.reason)
resolve(x)
} catch (error) {
reject(error)
}
})
this.onResolvedCallbacks.push(() => {
try {
let x = onFulFilled(this.value)
resolve(x)
} catch (error) {
reject(error)
}
})
}
})
return promise2
}
then返回一个promise
需要看promise的状态是成功还是失败,如果成功调用promise2的resolve,如果失败调用promise2的reject
定义一个函数resolvePromise,根据x的值来判断调用promise2的resolve还是reject
用定时器包裹的原因:promise属于微任务,settimeout属于宏任务,当微任务执行完毕才会执行宏任务,这样resolvePromise就能够获取到promise2的实例
对then方法进行改造
then(onFulFilled, onRejected) {
let promise2 = new myPromise((resolve, reject) => {
if (this.status === FULFILLED) {
setTimeout(() => {
try {
let x = onFulFilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0);
}
if (this.status === REJECTED) {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0);
}
if (this.status === PENDING) {
this.onRejectedCallbacks.push(() => {
// do something
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0);
})
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulFilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0);
})
}
})
return promise2
}
构造一个resolvePromise函数
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'))
}
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
let called = false // 保证改变状态后不可逆
// 有可能then方法是通过defineProperty实现的,取值时会发生异常,当发生异常的时候直接调用reject
try {
let then = x.then
if (typeof then === 'function') {
// 如果then是一个函数,那么认为x是一个promise,不写成x.then()是避免触发getter的时候发生异常
then.call(x, (y) => {
if (called) return
called = true
// 嵌套promise的情形,直到解析值为普通值为止
resolvePromise(promise2, y, resolve, reject)
}, (r) => {
if (called) return
called = true
reject(r)
})
} else {
resolve(x) // 如果then不是函数,那么认为x是一个普通值,直接调用resolve
}
} catch (err) {
if (called) return
called = true
reject(err)
}
} else {
// x是一个普通值
resolve(x)
}
}
DEMO
promise嵌套: 如果then方法return一个promise,这个promise的resolve方法里再传入一个promise
let promise = new myPromise((resolve, reject) => {
resolve(123)
}).then(result => {
return new myPromise((resolve, reject) => {
setTimeout(() => {
resolve(new myPromise((resolve,reject)=>{
setTimeout(() => {
resolve(200)
}, 1000);
}))
// resolve(200)
}, 1000);
})
}, reason => {
console.log('error', reason)
})
promise.then(data => {
console.log('data', data)
}, err => {
console.log('err', err)
})
// data 200
then方法参数可选
then方法的成功回调和失败回调是可选参数,如果不传默认设置为一个函数,这样一来可以实现穿透
then(onFulFilled, onRejected) {
onFulFilled = typeof onFulFilled === 'function' ? onFulFilled: v=>v
onRejected = typeof onRejected === 'function' ? onRejected : err=> {throw err}
...
}
DEMO
let promise= new myPromise((resolve,reject)=>{
reject(200)
}).then().then().then().then(
data=>{
console.log(data)
},
err=>{
console.log('err',err)
}
)
// err 200
测试和Promise延迟对象的使用
利用promises-aplus-test来测试
npm install promises-aplus-tests
配置script命令
"scripts": {
"build": "rollup -c -w",
"test": "promises-aplus-tests src/core.js"
},
然后在源码文件下写入
// 延迟对象,帮助减少一次套用
// dfd是一个对象 对象上创造一个promise
myPromise.deferred = function(){
let dfd = {}
dfd.promise = new myPromise((resolve,reject)=>{
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
// 【补充】延迟对象的应用,正常封装一个readFile
function readFile (filePath,encoding){
return new Promise((resolve,reject)=>{
fs.readFile(filePath,encoding,(err,data)=>{
if(err) return reject(err)
resolve(data)
})
})
}
// 使用延迟对象
function readFile(filePath, encoding) {
let dfd = myPromise.deferred()
fs.readFile(filePath, encoding, (err, data) => {
if (err) return dfd.reject(err)
dfd.resolve(data)
})
return dfd.promise
}
使用命令npm run test
一个promise直接resolve一个promise
源码实现
const resolve = (value) => {
// 如果resolve里直接嵌套一个promise,会等待里面promise执行完毕
if(value instanceof myPromise){
return value.then(resolve,reject)
}
if (this.status === PENDING) {
this.value = value
this.status = FULFILLED
this.onResolvedCallbacks.forEach(fn => fn())
}
}
DEMO
new myPromise((resolve,reject)=>{
resolve(new myPromise((resolve,reject)=>{
resolve(100)
}))
}).then(data=>{
console.log(data)
})
// 100
静态方法
直接通过类来调用
resolve
直接返回一个状态为成功的Promise对象
【补充】如果resolve接受一个promise,它会把等待这个promise执行完毕
源码实现
static resolve(value){
return new myPromise((resolve,reject)=>{
resolve(value)
})
}
DEMO
myPromise.resolve(new myPromise((resolve, reject) => {
setTimeout(() => {
resolve(1000)
}, 1000);
})).then(data=>{
console.log(data)
})
// 1000
reject
直接返回一个状态为失败的Promise对象
源码实现
static reject(reason){
return new myPromise((resolve,reject)=>{
reject(reason)
})
}
DEMO
myPromise.reject(new myPromise((resolve, reject) => {
setTimeout(() => {
resolve(1000)
}, 1000);
})).then(data => {
console.log(data)
}, err => {
console.log('err', err)
})
当一个promise直接reject一个promise的时候,并不会等待里面的promise执行完毕,而是把这个promise直接返回,在then方法的失败回调中当作参数接受这个promise,所以上述会打印一个promise
all
接受一个数组,这个数组的可以是由普通值或者promise组成,当全部成功的时候返回所有成功的结果组成的数组,只要有一项失败,那么就返回失败的结果
源码实现
static all(promises) {
return new myPromise((resolve, reject) => {
let res = []
let times = 0 // 计算成功的个数
const handleSuccess = (index, val) => {
res[index] = val
if(++times === promises.length){
resolve(res)
}
}
for (let i = 0; i < promises.length; i++) {
// 判断是否是promises,如果是调用then方法获取结果
let p = promises[i]
if (p.then && typeof p.then === 'function') {
p.then((data) => {
handleSuccess(i,data)
}, reject) // 如果某一个promise失败了 直接走失败即可
}else{
// 如果不是promise,直接把坐标和数据传入
handleSuccess(i,p)
}
}
})
}
DEMO
myPromise.all([1,2,3,new myPromise((resolve,reject)=>{
setTimeout(() => {
resolve('1')
}, 1000);
}),new myPromise((resolve,reject)=>{
setTimeout(() => {
reject('2')
}, 1000);
})]).then(data=>{
console.log(data)
},err=>{
console.log('err',err)
})
// err 2
myPromise.all([1,2,3,new myPromise((resolve,reject)=>{
setTimeout(() => {
resolve('1')
}, 1000);
}),new myPromise((resolve,reject)=>{
setTimeout(() => {
resolve('2')
}, 1000);
})]).then(data=>{
console.log(data)
},err=>{
console.log('err',err)
})
// [1, 2, 3, '1', '2']
race
接受一个数组,数组的每一项可以是promise或者普通值,如果数组中的某项更快获取到结果,那么race采用那个结果
【注意】race只是会采用最快获取的结果,但不阻碍数组中其他项的代码的执行,
static race(promises){
return new myPromise((resolve,reject)=>{
for(let i = 0 ; i < promises.length ; i++){
let p = promises[i]
if(p && typeof p.then === 'function'){
p.then(resolve,reject) // 一旦成功就直接 停止
}else{
resolve(p)
}
}
})
}
DEMO
myPromise.race([new myPromise((resolve,reject)=>{
setTimeout(() => {
resolve('1')
}, 5000);
}),new myPromise((resolve,reject)=>{
setTimeout(() => {
resolve('2')
}, 2000);
})]).then(data=>{
console.log(data)
},err=>{
console.log('err',err)
})
// 2
应用场景
超时处理,例如图片加载的时候如果请求时间过长,就不采用成功的结果
// 自己构造一个promise,暴露一个中断方法
function wrap(p1){
let abort // 把abort暴露出去,用户可以调用abort把p的状态改为reject,等价于p2失败
let p = new Promise((resolve,reject)=>{
abort = reject
})
let p2 = Promise.race([p1,p])
p2.abort = abort
return p2
}
原型方法
catch
catch(errFn){
return this.then(null,errFn)
}
finally
无论成功还是失败都会执行,但是可以向下执行,相当于then方法
- finally可以return一个promise,但是它不会采用这个promise成功的结果,但是如果这个promise状态为失败,会被下一个then方法的失败回调捕获
- finally不接受参数,即使传参也会显示undefined
以原生Promise为例子
let p1 = new Promise((resolve,reject)=>{
setTimeout(() => {
reject('失败')
}, 3000);
}).finally((data)=>{
console.log('finally',data)
// finally return一个promise 不采用这个promise成功的结果
return new Promise((resolve,reject)=>{
setTimeout(() => {
resolve(1000)
}, 1000);
})
}).then(data=>{
console.log('data',data)
}).catch(e=>{
console.log('catch',e)
})
但是如果finally返回的promise状态失败了,那么就会抛出错误
let p1 = new Promise((resolve,reject)=>{
setTimeout(() => {
reject('失败')
}, 3000);
}).finally((data)=>{
console.log('finally',data)
// finally return一个promise 不采用这个promise成功的结果,=
return new Promise((resolve,reject)=>{
setTimeout(() => {
reject(1000)
}, 1000);
})
}).then(data=>{
console.log('data',data)
}).catch(e=>{
console.log('catch',e)
})
源码实现
finally(cb){
// 这个data是上一次成功的结果
return this.then((data)=>{
// 如果cb返回的是一个promise,要等待这个promise执行完毕,所以用resolve方法包裹
// 并且把上一次成功的结果往下传递
// 并没有把cb执行完成功的结果n往下传递
return myPromise.resolve(cb()).then(()=>data)
},(err)=>{
// 上一个失败的结果往下传递
// 如果cb返回的是一个promise,如果这个promise状态为失败,就不走then方法向外抛出上一个失败的结果
// 而是直接reject,后面的catch或者下一个then的失败回调会捕获到这个错误
return myPromise.resolve(cb()).then(()=>{throw err})
})
}
Promisfy
将一个异步的方法转化成promise的形式,主要是给node来使用的,回调函数的参数第一个永远是err
function promisify(readFile){
return function(...args){
return new Promise((reslove,reject)=>{
readFile(...args,(err,data)=>{
if(err)reject(err)
reslove(data)
})
})
}
}
应用
const fs = require('fs')
let readFile = promisify(fs.readFile)
readFile('./src/code.js','utf8').then(data=>{
console.log(data)
})