对于JavaScript中的Promise大家肯定不会陌生? 那么它具体是怎么操作的呢?让我们大家一起来看看。 首先,promise是一个构造函数,需要传入一个executor执行器,executor会立刻执行,并且传入resolve和reject两个参数,promise有三个状态,fulfilled成功 reject失败 和 pedding等待状态(默认),pedding变成可以fulfilled和reject,fulfilled和reject两个状态不可改变,也可以通过resolve和reject来改变状态,每个promise都有then方法,可以访问成功的原因和失败的原因,当executor发生异常的时候,也会触发promise的失败,看具体的代码
JavaScript
const promise = new Promise((resolve, reject) => {
console.log('executor')
throw new Error('失败!')
resolve('success!')
reject('fail!')
})
promise.then((data) => {
console.log(data, 'success')
}, (reason) => {
console.log(reason, 'fail')
})
如果我们自己去写一个Promise来完成上述代码的话,以下步骤:
-
创建一个class名字叫做Promise 2 导出module.exports = Promise给其他的模块使用
-
里面写一个构造函数constructor带一个参数executor,并且执行executor()
-
在constructor新建两个函数resolve(value)成功和失败的函数和reject(reason)并且初始化value和reason变量赋值为undefined ->10. 在executor(resolve, reject)调用自动传入
-
在Promise之外新建3个状态的常量 PENDING FUIFLLED REJECT
-
在constructor里面初始化状态status
-
promise写then方法,接受两个参数onFufilled和onReject
-
在constructor里面设定两个参数value和reason为undefined,在resolve函数里面判断status是否等于PEDING,如果等于则把状态改为FUIFLLED并把value进行赋值,在reject函数里面判断status是否等于PENDING,如果等于则把状态改为REJECT,把reason进行赋值 注意:只有在状态是pedding的时候 才可以修改状态 和改变成功和失败的原因
-
在constructor里面捕获异常,try里面放入执行器executor(resolve, reject)catch里面放入reject(e)函数
-
then函数里面判断状态,如果是status=== FUIFLLED 调用onFufilled(this.value)函数,如果是status===REJECT,调用onReject(this.reason) // 进行测试;问题, 以上代码都是同步的逻辑,那么有异步代码怎么办?
-
问题: promise调用then的时候,可能状态依旧是pedding,那么我们需要将回调先存放起来,等待过一会儿调用resolve时触发 onResolveCallbacks执行,调用reject时触发 onRejectCallbacks执行 具体操作: 因为then方法可以调用多次,所以需要两个数组存放this.onResolveCallbacks=[]和this.onRejectCallbacks=[],并且在then函数 的status===PEDDING时将this.onResolveCallbacks.push(onFufilled),this.onRejectCallbacks.push(onReject) 可以改造成this.onResolveCallbacks.push(() => { onFufilled(this.value) }) this.onRejectCallbacks.push(() => { onReject(this.reason) })方便扩展功能
-
在resolve函数中执行上面的callback回调函数this.onResolveCallbacks.forEach(cb => cb()) 在reject函数中执行上面的callback回调函数this.onRejectCallbacks.forEach(cb =>cb()) 具体代码如下:
JavaScript
const PENDING = 'PENDING'
const FUIFLLED = 'FUIFLLED'
const REJECT = 'REJECTED'
class Promise {
constructor(executor) {
this.status = PENDING
this.value = undefined // 成功
this.reason = undefined // 失败
this.onResolveCallbacks = [] // 成功的回调
this.onRejectCallbacks = [] // 失败的回调
const resolve = (value) => {
if(this.status === PENDING) {
this.status = FUIFLLED
this.value = value
// 成功时调用成功的回调
this.onResolveCallbacks.forEach(cb => cb())
}
}
const reject = (reason) => {
if(this.status === PENDING) {
this.status = REJECT
this.reason = reason
// 成功时调用成功的回调
this.onRejectCallbacks.forEach(cb =>cb())
}
}
try {
executor(resolve, reject)
} catch(e) {
reject(e)
}
}
then(onFufilled, onReject) {
if(this.status === FUIFLLED) {
onFufilled(this.value)
}
if(this.status === REJECT) {
onReject(this.reason)
}
if(this.status === PENDING) {
this.onResolveCallbacks.push(() => {
onFufilled(this.value)
})
this.onRejectCallbacks.push(() => {
onReject(this.reason)
})
}
}
}
module.exports = Promise
那么如何解决我们常用的嵌套地狱,进行链式调用呢?继续往下看一个场景
JavaScript
const fs = require('fs')
const path = require('path')
fs.readFile(path_resolve(__dirname, 'a.txt'), 'utf-8', function(err, data) => {
if(err) return console.log(err)
fs.readFile(data, 'utf-8', function(err, data) {
if(err) return console.log(err)
console.log(data)
})
})
上面的代码容易造成一个问题,函数嵌套多的话造成回调地狱问题,我么可以新增一个promise来解决上述问题,改成为
JavaScript
function readFile(filepath) {
let promise = new Promise((resolve, reject) => {
fs.readFile(filepath, 'utf-8', function(err, data) {
if(err) return reject(err)
resolve(data)
})
})
return promise
}
readFile(path.resolve(__dirname, 'a.txt')).then((data) => {
}, err => {
})
具体步骤解释为: 1. 建立函数readFile接收一个文件路径参数 ->4. 在函数里面调用fs.readFile读取 3. 在外面调用readFile函数,传递一个路径参数进去,并链式调用then 4. 因为readFile里面链式调用then,所以readFile函数内部必须新建promise并返回出去,把fs.readFile放入到promise内部 5. 改造fs.readFile回调函数,如果有err则reject(err),读取成功则用resolve(data)
以上方法有一个问题,readFile单独写一个,那么我如果要写一个writeFile又得重新写一个函数,这样做太麻烦了,那么要怎么解决这个问题呢?
JavaScript
const { promisify } = require('util')
let readFile = promisify(fs.readFile) // 高阶函数
这样异步函数就帮我们转换成promise的形式了,它的返回值是一个函数 那么promisify它到底是怎么实现的呢?看下列代码,其实在外面包装了一个高阶函数
JavaScript
function promisify(fn) { // fn ====> fs.readFile
return function(...args) { // readFile
let promise = new Promise((resolve, reject) => {
fn(...args, function(err, data) {
if(err) return reject(err)
resolve(data)
})
})
return promise
}
}
let readFile = promisify(fs.readFile) // 高阶函数
readFile(path.resolve(__dirname, 'a.txt'), 'utf8').then((data) => {
console.log(data) // aaaaaa
}, err => {
})
执行成功了! 看下面案例,将异步调用拍平化!
JavaScript
// 1) then链的特点,当then中成功和失败的回调函数返回的是一个promise,内部会解析这个promise,并且将结果传递
// 到外层的下一个then中
// 2) 下一次then成功还是失败,取决于当前promise状态
// 3) 如果成功和失败返回是不是一个promise,那么这个结果会直接传递到下一个人的成功
// 4) 如果成功和失败的回调中抛出异常 则会执行下一个then的失败
readFile(path.resolve(__dirname, './a.txt'), 'utf8').then((data) => {
return readFile(data+'.txt', 'utf8')
}).then((data) => {
console.log('data', 'success')
}, (err) => {
console.log(err, 'fail')
return true
}).then((data) => {
console.log(data, 'success')
throw new Error('错误')
}).then(() => {}, (err) => {
console.log(err)
})
// 总结: 让promise(then)失败有两种方式 一种是抛异常 返回一个失败的promise
写一个案例,如何实现下列的代码逻辑
JavaScript
let promise = new Promise((resolve, reject) => {
resolve(100)
})
promise.then((data) => {
return data
}).then((data) => {
console.log(data, 'success')
})
于是可以改造上面then方法的代码
JavaScript
let promise2 = new Promise((resolve, reject) => {
if(this.status === FUIFLLED) {
try {
let x = onFufilled(this.value)
resolve(x)
} catch (e) {
reject(e)
}
}
if(this.status === REJECT) {
try {
let x = onReject(this.reason)
resolve(x)
} catch (e) {
reject(e)
}
}
if(this.status === PENDING) {
this.onResolveCallbacks.push(() => {
try {
let x = onFufilled(this.value)
resolve(x)
} catch (e) {
reject(e)
}
})
this.onRejectCallbacks.push(() => {
try {
let x = onReject(this.reason)
resolve(x)
} catch (e) {
reject(e)
}
})
}
})
return promise2
思路步骤如下:
- 在then方法新建一个promise((resolve, reject))
- 在then的状态为pending中resolve成功函数里面返回一个普通值并且接受resolve(返回的值),方便在下一个then链式当中成功函数获取
- 在then的状态为pending中reject成功函数里面返回一个普通值并且接受resolve(返回的值),方便在下一个then链式当中成功函数获取
- 如果是出错了,则增加try catch 来捕获异常
那么问题又来了,如果then里面返回是一个promise怎么办? 解决步骤:
- 新建一个函数resolvePromise并且接收四个参数
- 注意点,取不到promise2的值,因为是同步代码,先执行,然后在返回promise2,怎么解决呢? 在里面包一个setTimeout,因为promise里面先执行,所以可以获取promise2
- 传递过来的promise需要兼容别人写的和自己写的,所以需要判断promise的类型 完整版代码如下:
JavaScript
const PENDING = 'PENDING'
const FUIFLLED = 'FUIFLLED'
const REJECT = 'REJECTED'
function resolvePromise(promise2, x, resolve, reject) {
// console.log(promise2, x, resolve, reject)
// 如果x和promise 引用的是同一个对象,那么promise2 要等待x执行完毕
// x是一个promise,而且永远不会成功和失败,那么就在这里等待
if(x === promise2) return reject(new TypeError('出错了'))
// 我如何知道x是不是promise
if((typeof x === 'object' && x !== null) || typeof x === 'function') {
// 有可能是promise
let called = false
try {
let then = x.then // 取then的时候会报错 直接失败 第34 行
if(typeof then === 'function') {
then.call(x, (y) => {
// 为了防止promise解析后的结果依然是promise,所以需要递归解析
if(called) return
called = true
resolvePromise(promise2, y, resolve, reject)
}, (r) => {
if(called) return
called = true
reject(r)
})
} 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.onResolveCallbacks = [] // 成功的回调
this.onRejectCallbacks = [] // 失败的回调
const resolve = (value) => {
if(this.status === PENDING) {
this.status = FUIFLLED
this.value = value
// 成功时调用成功的回调
this.onResolveCallbacks.forEach(cb => cb())
}
}
const reject = (reason) => {
if(this.status === PENDING) {
this.status = REJECT
this.reason = reason
// 成功时调用成功的回调
this.onRejectCallbacks.forEach(cb =>cb())
}
}
try {
executor(resolve, reject)
} catch(e) {
reject(e)
}
}
then(onFufilled, onReject) {
onFufilled = typeof onFufilled === 'function' ? onFufilled : v => v
onReject = typeof onReject === 'function' ? onReject : e => {throw e}
let promise2 = new Promise((resolve, reject) => {
if(this.status === FUIFLLED) {
setTimeout(() => {
try {
let x = onFufilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
if(this.status === REJECT) {
setTimeout(() => {
try {
let x = onReject(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
if(this.status === PENDING) {
this.onResolveCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFufilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
this.onRejectCallbacks.push(() => {
setTimeout(() => {
try {
let x = onReject(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
})
return promise2
}
}
module.exports = Promise
怎么判断上面的promise代码是否符合规范呢,有一个包可以帮助我们检测promise-aplus-tests 安装包 npm install promise-aplus-tests -g 具体代码
JavaScript
Promise.deferred = function() {
let dfd = {}
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
执行指令 promise-aplus-tests 当前路径 完成成功!