Promise由来
在Promise
出现以前,我们通常主主要的解决异步问题的方式就是通过回调嵌套,在异步逻辑执行完毕之后通过回调函数的方式获取异步执行结果. 层层回调嵌套会使得代码逻辑不直观也不利于后期开发维护.
fs.readFile("./a.txt", (err, data) => {
fs.readFile(data, (err, data) => {
fs.readFile(data, (err, data) => {
//回调黑洞
})
})
})
复制代码
Promise
巧妙的将异步回调形式的层层嵌套,转变成同步回调形式的链式调用.本质上Promise
内部用的还是回调函数的方式,解决异步问题, 但是可以通过同步的方式体现.
new Promise((reslove, reject)=> {
//异步逻辑
}).then(
//异步逻辑
).then(
//异步逻辑
).then(
)
复制代码
同步版的Promise
直接上Promise
的源码吧, Promise
的源码都是根据Promise/A+规范来实现的
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected'
function Promise(executor) {
/**
*Promise对象内部有一个状态, 默认为初始状态pending,由Promise的执行结果操作状态的改变, 而且整个过程状态只能变化一次,要么是由等待态pending变成成功态resolved,要么是由等待态变成失败态rejected;
*/
this.state = PENDING;
this.value = undefined;
this.reason = undefined;
let resolve = (value) => {
if (this.state === PENDING) { //只能是在pending的时候才能改变状态
this.state = RESOLVED;
this.value = value;
}
}
let reject = (reason) => {
if (this.state === PENDING) {
this.state = REJECTED;
this.reason = reason;
}
}
/**
* Promise接收一个函数作为参数,这个函数会在new Promise时立即执行, 并且该函数接收两个函数作为参数,在 Promise操作成功的时候调用第一个参数resolve, 在操作失败的时候调用reject
*/
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
Promise.prototype.then = function (onResolved, onRejected) {
/**
* 生成的每个Promise对象都具有then方法, then方法也同样接受两个函数操作参数,在调用then方法时,如果此时的状态为resolved则调用执行第一个函数onResolved, 当此时的状态时rejected时执行第二个函数onRejected
*/
if (this.state === RESOLVED) {
onResolved(this.value)
}
if (this.state === REJECTED) {
onRejected(this.reason)
}
}
复制代码
但是上面的Promise
是一个基础版本,只能解决同步问题, 因为生成的Promise
对象在调用then
方法时,此时的状态state已经发生改变,可以执行对应的逻辑, 但是思考一下,如果executo
r函数内执行的是一个异步操作, 那么生成promise
对象在调用then
时,改变状态的两个函数resolve
、reject
都没有被调用, 此时的状态state还是等待态pending.
通常我们遇到这种问题的处理方式都是通过回调来解决, 可以先把then
方法内的两个函数存放起来, 在异步操作执行完毕后,即在executor
接收的两个参数函数被调用时,再回过头去执行then内暂存的方法:
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected'
function Promise(executor) {
this.state = PENDING;
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = []; //存放then的第一个参数, 即状态为成功时要调用的回调函数
this.onRejectedCallbacks = []; //存放then的第二个参数, 即状态为失败时要调用的回调函数
let resolve = (value) => {
if (this.state === PENDING) {
this.state = RESOLVED;
this.value = value;
//在调用resolve,即在Promise状态为成功时执行then内的onResolved函数
this.onRejectedCallbacks.forEach(fn => fn())
}
}
let reject = (reason) => {
if (this.state === PENDING) {
this.state = REJECTED;
this.reason = reason;
//在调用reject,即在Promise状态为失败时执行then内的onRejected函数
this.onRejectedCallbacks.forEach(fn => fn())
}
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
Promise.prototype.then = function (onResolved, onRejected) {
if (this.state === RESOLVED) {
onResolved(this.value)
}
if (this.state === REJECTED) {
onRejected(this.reason)
}
//对于异步操作, 那么此时的状态肯定还是等待态, 先将处理函数存放, 带异步操作执行完毕后,再回过来执行
if (this.state === PENDING) {
this.onResolvedCallbacks.push(onResolved)
this.onRejectedCallbacks.push(onRejected)
}
}
复制代码
这里大家可能有些疑问
-
代码第10、11行为啥要用数组接收? 本来就一个函数, 直接用个变量保存不就行了么?
//同一个promise对象的then方法可以被多次调用, 当多次调用then方法时,就需要把每个回调都接收 var promise = new Promise(/**/) promise.then() promise.then() 复制代码
-
为啥在调用执行
then
方法时,同步操作的状态改变, 而异步操作状态没改变还是pending?var p = new Promise((resolve, reject) => { /* 代码块1 */}) p.then(/*代码块2*/) 复制代码
先理一下上面这块假代码的执行顺序: 在用
new
操作符调用Promise
时, 先执行代码块1 ,执行完毕之后生成一个对象p
,然后对象p
调用执行then
时,开始执行代码块2; 如果上面代码块中的操作都是同步的,那就没啥悬念,代码从上到下依次执行. 但是当代码块1是一个异步操作时,js引擎的执行顺序是先把当前的同步的代码执行完,才会去掉用执行异步代码, 所以如果代码块1是异步操作,那么在调用then方法时,状态还没有改变.
继续, 还没完…, 接下来的才是重点.
Promise的链式调用和值的穿透特性
我们在使用Promise
时发现他的then
方法可以一直不断的链式调用,而且上一个then
方法内参数的返回值能够在下一个then
方法内使用,甚至下下个then
方法内使用,这里是如何做到的呢?
链式调用简单, 我们直接在调用then
方法时再返回一个promise
对象就能实现链式调用了;对于then
方法的值的穿透特性,如果我们能够把当前then
的参数函数返回值传递到新返回的promise
对象中,当这个新返回的promise
对象调用自己的resolve
/reject
方法后不就可以在新返回的promise
对象的then
中使用了么, 有点绕哦……还是直接上代码吧; 由于构造函数Promise
的代码还是上面那块,没有变化这里就不复制过来了, 这里变化的主要是then
函数,和处理值的resolvePromise
函数.
根据Promise/A+规范一个Promise
必须提供一个then
函数,用于处理它的value
或reason
, 并且这个Promise
函数接收两个可选的函数作为参数promise.then(onFulfilled, onRejected)
-
如果
onFulfilled
或者onRejected
不是一个函数, 那么就直接忽略掉 -
如果
onFulfilled
是一个函数它必须在
promise
的状态是resolved
之后被调用,并且用promise
的value
值作为它的第一参数它肯定不会在
promise
的状态是resolved
之前被调用它的调用肯定不会超过一次
-
如果
onRejected
是一个函数它必须在
promise
的状态是rejected
之后被调用,并且用promise
的reason
值作为它的第一参数它肯定不会在
promise
的状态是rejected
之前被调用它的调用肯定不会超过一次
-
onFulfilled
或者onRejected
要在当前执行上下文的代码执行完毕之后再被调用(可以理解为异步调用) -
onFulfilled
或者onRejected
必须作为一个函数被调用 -
then
函数可能会被同一个promise
对象多次调用如果当
promise
的状态是resolved
, 所有各自的onFulfilled
方法都会被按照调用then
的顺序依次调用执行如果当
promise
的状态是rejected
, 所有各自的onFejected
方法都会被按照调用then
的顺序依次调用执行 -
then
函数必须返回一个promise
即:promise2 = promise1.then(onFulfilled, onRejected)
如果
onFulfilled
或者onRejected
返回了一个值x
, 那么执行resolvePromise(promise2, x)
如果
onFulfilled
或者onRejected
抛出了一个异常e
,promise2
必须变成rejected
状态,并把这个e
作为reason
如果
onFulfilled
不是一个函数并且promise1
是一个成功态resolved
,promise2
必须变成resolved
状态,并且使用promise1
的value
,作为promise2
的value
如果
onFulfilled
不是一个函数并且promise1
是一个失败态rejected
,promise2
必须变也成rejected
状态,并且使用promise1
的reason
,作为promise2
的reason
Promise.prototype.then = function (onResolved, onRejected) {
//根据Promise/A+规定then方法的两个参数函数是可选的,而且如果传入的不是函数会忽略掉, 用空函数替代
onResolved = typeof onResolved === 'function' ? onResolved : v => v
onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err }
let promise2 = new Promise((resolve, reject) => {
if (this.state === RESOLVED) {
//为了拿到promise2,需要先执行完同步代码才能生成promise2对象, 否则promise2就是undefined
setTimeout(() => {
//try...catch只能捕获同步异常,无法捕获异步异常
try {
const x = onResolved(this.value)
//处理及判断x值类型, 好决定到底用promise2的哪一个状态改变函数调用
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
}
if (this.state === REJECTED) {
setTimeout(() => {
try {
const x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
}
if (this.state === PENDING) {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onResolved(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
}
})
return promise2
}
复制代码
resolvePromise
函数用于处理then函数参数的返回值, 根据返回值类型决定返回的下一个Promise
的状态,从而决定到底执行下一个then
的哪个参数函数,根据Promise/A+规范
-
如果
promise2
和x
指向同一个对象,调用promise2
的reject
并把这个异常作为promise2
的reason
-
如果返回的
x
值是一个promise
, 那就采用它的状态如果
x
的状态是pending
,promise2
也必须保持pending
状态,直到x
的状态变成resolved
或者rejected
如果
x
的状态是resolved
,调用promise2
的resolve
方法,接收当前value
作为第一个参数如果
x
的状态是rejected
,调用promise2
的reject
方法, 接收当前的reason
作为第一个参数 -
如果返回的
x
是一个函数或对象判断是否有
then
属性,如果这个then
属性是一个函数,那么就认定这个x是一个promise
对象, 如果没有then
属性,或者then
属性不是一个函数, 那么也把x
作为普通值进行处理,直接调用promise2
的resolve(x)
-
如果返回的
x
不是一个函数或对象,那么就作为普通值处理,直接调用promise2
的resolve(x)
function resolvePromise(promise2, x, resolve, reject) {
//在一个promise中resolve/reject最多只能调用一次
let called;
if (promise2 === x) {
if (called) return;
called = true
reject(new TypeError("TypeError"))
}
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
try {
let then = x.then
if (typeof then === 'function') {
//这里就认定x是一个promise, 因为没法再进一步判断了
then.call(x, v => {
if (called) return;
called = true
resolvePromise(promise2, v, resolve, reject)
}, r => {
if (called) return;
called = true
reject(r)
})
} else {
if (called) return;
called = true
resolve(x)
}
} catch (e) {
if (called) return;
called = true
reject(e)
}
} else {
if (called) return;
called = true
resolve(x)
}
}
复制代码
代码不是很复杂就不一句句解释了,这里就直接说我在刚开始学习时遇到的疑问
-
then
方法代码第3、4行为啥then
函数的第一个参数缺席用空函数替代, 而第二个参数缺席却需要抛出个异常?首先
then
函数接收两个函数作为参数, 最终then
接收的两个函数只会执行其中的一个,具体执行哪一个函数取决于当前Promise
的内部状态, 当Promise
内部状态为resolved
时执行第一个函数, 当Promise
内部状态为rejected
时执行第二个函数, 此外需要注意当executor
函数在执行过程中抛异常的话,内部状态也会变成rejected
;根据Promise/A+规范,如果在执行
promise1
的onFulfilled
或者onRejected
时抛出了一个异常e
, 会使promise2
的状态变成rejected
,并调用执行promise2
对象的onRejected
-
为啥
then
函数中的有些代码需要放在定时器函数里?为了拿到
promise2
, 就必须使promise2
的构造函数执行完,所以只能让同步代码先执行完, 生成promise2
对象, 再回过来执行构造函数里的异步代码. -
为啥在
resolvePromise
代码里用then.call(x)
调用, 而不是直接用x.then
?// 如果是通过下面的方式定义的属性then,在第一次获取时不会报错, 但在第二次获取时就会报错 let times = 1 Object.defineProperty(x, 'then', getter() { if(times>1) { times++ return new Error() } return () => {} } ) 复制代码