Promise 必须为如下三种状态之一:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)。一旦Promise 被 resolve 或 reject,不能再迁移至其余任何状态(即状态 immutable)。前端
基本过程:git
真正的链式Promise是指在当前promise达到fulfilled状态后,即开始进行下一个promise.es6
先从 Promise 执行结果看一下,有以下一段代码:github
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ test: 1 })
resolve({ test: 2 })
reject({ test: 2 })
}, 1000)
}).then((data) => {
console.log('result1', data)
},(data1)=>{
console.log('result2',data1)
}).then((data) => {
console.log('result3', data)
})
//result1 { test: 1 }
//result3 undefined
复制代码
显然这里输出了不一样的 data。由此能够看出几点:数组
基于以上几点,咱们先写个基于 PromiseA+ 规范的只含 resolve 方法的 Promise 模型:promise
function Promise(fn){
let state = 'pending';
let value = null;
const callbacks = [];
this.then = function (onFulfilled){
return new Promise((resolve, reject)=>{
handle({ //桥梁,将新 Promise 的 resolve 方法,放到前一个 promise 的回调对象中
onFulfilled,
resolve
})
})
}
function handle(callback){
if(state === 'pending'){
callbacks.push(callback)
return;
}
if(state === 'fulfilled'){
if(!callback.onFulfilled){
callback.resolve(value)
return;
}
const ret = callback.onFulfilled(value) //处理回调
callback.resolve(ret) //处理下一个 promise 的resolve
}
}
function resolve(newValue){
const fn = ()=>{
if(state !== 'pending')return
state = 'fulfilled';
value = newValue
handelCb()
}
setTimeout(fn,0) //基于 PromiseA+ 规范
}
function handelCb(){
while(callbacks.length) {
const fulfiledFn = callbacks.shift();
handle(fulfiledFn);
};
}
fn(resolve)
}
复制代码
这个模型简单易懂,这里最关键的点就是在 then 中新建立的 Promise,它的状态变为 fulfilled 的节点是在上一个 Promise的回调执行完毕的时候。也就是说当一个 Promise 的状态被 fulfilled 以后,会执行其回调函数,而回调函数返回的结果会被看成 value,返回给下一个 Promise(也就是then 中产生的 Promise),同时下一个 Promise的状态也会被改变(执行 resolve 或 reject),而后再去执行其回调,以此类推下去...链式调用的效应就出来了。浏览器
可是若是仅仅是例子中的状况,咱们能够这样写:异步
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ test: 1 })
}, 1000)
}).then((data) => {
console.log('result1', data)
//dosomething
console.log('result3')
})
//result1 { test: 1 }
//result3
复制代码
实际上,咱们经常使用的链式调用,是用在异步回调中,以解决"回调地狱"的问题。以下例子:函数
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ test: 1 })
}, 1000)
}).then((data) => {
console.log('result1', data)
//dosomething
return test()
}).then((data) => {
console.log('result2', data)
})
function test(id) {
return new Promise(((resolve) => {
setTimeout(() => {
resolve({ test: 2 })
}, 5000)
}))
}
//基于第一个 Promise 模型,执行后的输出
//result1 { test: 1 }
//result2 Promise {then: ƒ}
复制代码
用上面的 Promise 模型,获得的结果显然不是咱们想要的。认真看上面的模型,执行 callback.resolve 时,传入的参数是 callback.onFulfilled 执行完成的返回,显然这个测试例子返回的就是一个 Promise,而咱们的 Promise 模型中的 resolve 方法并无特殊处理。那么咱们将 resolve 改一下:学习
function Promise(fn){
...
function resolve(newValue){
const fn = ()=>{
if(state !== 'pending')return
if(newValue && (typeof newValue === 'object' || typeof newValue === 'function')){
const {then} = newValue
if(typeof then === 'function'){
// newValue 为新产生的 Promise,此时resolve为上个 promise 的resolve
//至关于调用了新产生 Promise 的then方法,注入了上个 promise 的resolve 为其回调
then.call(newValue,resolve)
return
}
}
state = 'fulfilled';
value = newValue
handelCb()
}
setTimeout(fn,0)
}
...
}
复制代码
用这个模型,再测试咱们的例子,就获得了正确的结果:
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ test: 1 })
}, 1000)
}).then((data) => {
console.log('result1', data)
//dosomething
return test()
}).then((data) => {
console.log('result2', data)
})
function test(id) {
return new Promise(((resolve, reject) => {
setTimeout(() => {
resolve({ test: 2 })
}, 5000)
}))
}
//result1 { test: 1 }
//result2 { test: 2 }
复制代码
显然,新增的逻辑就是针对 resolve 入参为 Promise 的时候的处理。咱们观察一下 test 里面建立的 Promise,它是没有调用 then方法的。从上面的分析咱们已经知道 Promise 的回调函数就是经过调用其 then 方法注册的,所以 test 里面建立的 Promise 其回调函数为空。
显然若是没有回调函数,执行 resolve 的时候,是没办法链式下去的。所以,咱们须要主动为其注入回调函数。
咱们只要把第一个 then 中产生的 Promise 的 resolve 函数的执行,延迟到 test 里面的 Promise 的状态为 onFulfilled 的时候再执行,那么链式就能够继续了。因此,当 resolve 入参为 Promise 的时候,调用其 then 方法为其注入回调函数,而注入的是前一个 Promise 的 resolve 方法,因此要用 call 来绑定 this 的指向。
基于新的 Promise 模型,上面的执行过程产生的 Promise 实例及其回调函数,能够用看下表:
Promise | callback |
---|---|
P1 | [{onFulfilled:c1(第一个then中的fn),resolve:p2resolve}] |
P2 (P1 调用 then 时产生) | [{onFulfilled:c2(第二个then中的fn),resolve:p3resolve}] |
P3 (P2 调用 then 时产生) | [] |
P4 (执行c1中产生[调用 test ]) | [{onFulfilled:p2resolve,resolve:p5resolve}] |
P5 (调用p2resolve 时,进入 then.call 逻辑中产生) | [] |
有了这个表格,咱们就能够清晰知道各个实例中 callback 执行的顺序是:
c1 -> p2resolve -> c2 -> p3resolve -> [] -> p5resolve -> []
以上就是链式调用的原理了。
下面咱们再来补全 reject 的逻辑。只须要在注册回调、状态改变时加上 reject 的逻辑便可。
完整代码以下:
function Promise(fn){
let state = 'pending';
let value = null;
const callbacks = [];
this.then = function (onFulfilled,onRejected){
return new Promise((resolve, reject)=>{
handle({
onFulfilled,
onRejected,
resolve,
reject
})
})
}
function handle(callback){
if(state === 'pending'){
callbacks.push(callback)
return;
}
const cb = state === 'fulfilled' ? callback.onFulfilled:callback.onRejected;
const next = state === 'fulfilled'? callback.resolve:callback.reject;
if(!cb){
next(value)
return;
}
const ret = cb(value)
next(ret)
}
function resolve(newValue){
const fn = ()=>{
if(state !== 'pending')return
if(newValue && (typeof newValue === 'object' || typeof newValue === 'function')){
const {then} = newValue
if(typeof then === 'function'){
// newValue 为新产生的 Promise,此时resolve为上个 promise 的resolve
//至关于调用了新产生 Promise 的then方法,注入了上个 promise 的resolve 为其回调
then.call(newValue,resolve, reject)
return
}
}
state = 'fulfilled';
value = newValue
handelCb()
}
setTimeout(fn,0)
}
function reject(error){
const fn = ()=>{
if(state !== 'pending')return
if(error && (typeof error === 'object' || typeof error === 'function')){
const {then} = error
if(typeof then === 'function'){
then.call(error,resolve, reject)
return
}
}
state = 'rejected';
value = error
handelCb()
}
setTimeout(fn,0)
}
function handelCb(){
while(callbacks.length) {
const fn = callbacks.shift();
handle(fn);
};
}
fn(resolve, reject)
}
复制代码
异常一般是指在执行成功/失败回调时代码出错产生的错误,对于这类异常,咱们使用 try-catch 来捕获错误,并将 Promise 设为 rejected 状态便可。
handle代码改造以下:
function handle(callback){
if(state === 'pending'){
callbacks.push(callback)
return;
}
const cb = state === 'fulfilled' ? callback.onFulfilled:callback.onRejected;
const next = state === 'fulfilled'? callback.resolve:callback.reject;
if(!cb){
next(value)
return;
}
try {
const ret = cb(value)
next(ret)
} catch (e) {
callback.reject(e);
}
}
复制代码
咱们实际使用时,常习惯注册 catch 方法来处理错误,例:
new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ test: 1 })
}, 1000)
}).then((data) => {
console.log('result1', data)
//dosomething
return test()
}).catch((ex) => {
console.log('error', ex)
})
复制代码
实际上,错误也好,异常也罢,最终都是经过reject实现的。也就是说能够经过 then 中的错误回调来处理。因此咱们能够增长这样的一个 catch 方法:
function Promise(fn){
...
this.then = function (onFulfilled,onRejected){
return new Promise((resolve, reject)=>{
handle({
onFulfilled,
onRejected,
resolve,
reject
})
})
}
this.catch = function (onError){
this.then(null,onError)
}
...
}
复制代码
在实际应用的时候,咱们很容易会碰到这样的场景,无论Promise最后的状态如何,都要执行一些最后的操做。咱们把这些操做放到 finally 中,也就是说 finally 注册的函数是与 Promise 的状态无关的,不依赖 Promise 的执行结果。因此咱们能够这样写 finally 的逻辑:
function Promise(fn){
...
this.catch = function (onError){
this.then(null,onError)
}
this.finally = function (onDone){
this.then(onDone,onDone)
}
...
}
复制代码
实际应用中,咱们可使用 Promise.resolve 和 Promise.reject 方法,用于将于将非 Promise 实例包装为 Promise 实例。以下例子:
Promise.resolve({name:'winty'})
Promise.reject({name:'winty'})
// 等价于
new Promise(resolve => resolve({name:'winty'}))
new Promise((resolve,reject) => reject({name:'winty'}))
复制代码
这些状况下,Promise.resolve 的入参可能有如下几种状况:
基于以上几点,咱们能够实现一个 Promise.resolve 方法以下:
function Promise(fn){
...
this.resolve = function (value){
if (value && value instanceof Promise) {
return value;
} else if (value && typeof value === 'object' && typeof value.then === 'function'){
let then = value.then;
return new Promise(resolve => {
then(resolve);
});
} else if (value) {
return new Promise(resolve => resolve(value));
} else {
return new Promise(resolve => resolve());
}
}
...
}
复制代码
Promise.reject与Promise.resolve相似,区别在于Promise.reject始终返回一个状态的rejected的Promise实例,而Promise.resolve的参数若是是一个Promise实例的话,返回的是参数对应的Promise实例,因此状态不一 定。 所以,reject 的实现就简单多了,以下:
function Promise(fn){
...
this.reject = function (value){
return new Promise(function(resolve, reject) {
reject(value);
});
}
...
}
复制代码
入参是一个 Promise 的实例数组,而后注册一个 then 方法,而后是数组中的 Promise 实例的状态都转为 fulfilled 以后则执行 then 方法。这里主要就是一个计数逻辑,每当一个 Promise 的状态变为 fulfilled 以后就保存该实例返回的数据,而后将计数减一,当计数器变为 0 时,表明数组中全部 Promise 实例都执行完毕。
function Promise(fn){
...
this.all = function (arr){
var args = Array.prototype.slice.call(arr);
return new Promise(function(resolve, reject) {
if(args.length === 0) return resolve([]);
var remaining = args.length;
function res(i, val) {
try {
if(val && (typeof val === 'object' || typeof val === 'function')) {
var then = val.then;
if(typeof then === 'function') {
then.call(val, function(val) {
res(i, val);
}, reject);
return;
}
}
args[i] = val;
if(--remaining === 0) {
resolve(args);
}
} catch(ex) {
reject(ex);
}
}
for(var i = 0; i < args.length; i++) {
res(i, args[i]);
}
});
}
...
}
复制代码
有了 Promise.all 的理解,Promise.race 理解起来就更容易了。它的入参也是一个 Promise 实例数组,而后其 then 注册的回调方法是数组中的某一个 Promise 的状态变为 fulfilled 的时候就执行。由于 Promise 的状态只能改变一次,那么咱们只须要把 Promise.race 中产生的 Promise 对象的 resolve 方法,注入到数组中的每个 Promise 实例中的回调函数中便可。
function Promise(fn){
...
this.race = function(values) {
return new Promise(function(resolve, reject) {
for(var i = 0, len = values.length; i < len; i++) {
values[i].then(resolve, reject);
}
});
}
...
}
复制代码
Promise 源码不过几百行,咱们能够从执行结果出发,分析每一步的执行过程,而后思考其做用便可。其中最关键的点就是要理解 then 函数是负责注册回调的,真正的执行是在 Promise 的状态被改变以后。而当 resolve 的入参是一个 Promise 时,要想链式调用起来,就必须调用其 then 方法(then.call),将上一个 Promise 的 resolve 方法注入其回调数组中。
虽然 then 广泛认为是微任务。可是浏览器没办法模拟微任务,目前要么用 setImmediate ,这个也是宏任务,且不兼容的状况下仍是用 setTimeout 打底的。还有,promise 的 polyfill (es6-promise) 里用的也是 setTimeout。所以这里就直接用 setTimeout,以宏任务来代替微任务了。
function Promise(fn) {
let state = 'pending'
let value = null
const callbacks = []
this.then = function (onFulfilled, onRejected) {
return new Promise((resolve, reject) => {
handle({
onFulfilled,
onRejected,
resolve,
reject,
})
})
}
this.catch = function (onError) {
this.then(null, onError)
}
this.finally = function (onDone) {
this.then(onDone, onError)
}
this.resolve = function (value) {
if (value && value instanceof Promise) {
return value
} if (value && typeof value === 'object' && typeof value.then === 'function') {
const { then } = value
return new Promise((resolve) => {
then(resolve)
})
} if (value) {
return new Promise(resolve => resolve(value))
}
return new Promise(resolve => resolve())
}
this.reject = function (value) {
return new Promise(((resolve, reject) => {
reject(value)
}))
}
this.all = function (arr) {
const args = Array.prototype.slice.call(arr)
return new Promise(((resolve, reject) => {
if (args.length === 0) return resolve([])
let remaining = args.length
function res(i, val) {
try {
if (val && (typeof val === 'object' || typeof val === 'function')) {
const { then } = val
if (typeof then === 'function') {
then.call(val, (val) => {
res(i, val)
}, reject)
return
}
}
args[i] = val
if (--remaining === 0) {
resolve(args)
}
} catch (ex) {
reject(ex)
}
}
for (let i = 0; i < args.length; i++) {
res(i, args[i])
}
}))
}
this.race = function (values) {
return new Promise(((resolve, reject) => {
for (let i = 0, len = values.length; i < len; i++) {
values[i].then(resolve, reject)
}
}))
}
function handle(callback) {
if (state === 'pending') {
callbacks.push(callback)
return
}
const cb = state === 'fulfilled' ? callback.onFulfilled : callback.onRejected
const next = state === 'fulfilled' ? callback.resolve : callback.reject
if (!cb) {
next(value)
return
}
try {
const ret = cb(value)
next(ret)
} catch (e) {
callback.reject(e)
}
}
function resolve(newValue) {
const fn = () => {
if (state !== 'pending') return
if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
const { then } = newValue
if (typeof then === 'function') {
// newValue 为新产生的 Promise,此时resolve为上个 promise 的resolve
// 至关于调用了新产生 Promise 的then方法,注入了上个 promise 的resolve 为其回调
then.call(newValue, resolve, reject)
return
}
}
state = 'fulfilled'
value = newValue
handelCb()
}
setTimeout(fn, 0)
}
function reject(error) {
const fn = () => {
if (state !== 'pending') return
if (error && (typeof error === 'object' || typeof error === 'function')) {
const { then } = error
if (typeof then === 'function') {
then.call(error, resolve, reject)
return
}
}
state = 'rejected'
value = error
handelCb()
}
setTimeout(fn, 0)
}
function handelCb() {
while (callbacks.length) {
const fn = callbacks.shift()
handle(fn)
}
}
fn(resolve, reject)
}
复制代码
以为内容有帮助能够关注下个人公众号 「前端Q」,一块儿学习成长~~