promise规范及应用

382 阅读11分钟

目标

  • promises/A+ 规范讲解
  • 实现一个简易版本的promise函数

知识要点

术语

  • promise是一个有then方法的对象或者函数
  • thenable是一个有then方法的对象或者函数
  • value是promise状态成功时的值,作为resolve的入参,包括各种数据类型,undefined, thenable,或者promise
  • reason是promise状态失败时的值,作为reject的入参,表示拒绝的原因
  • exception 是一个使用throw 抛出的异常的值

规范

Promise States

States转换
pending (pending->resolve(value)->fufilledpending->rejected(reason)->rejected)- 初始状态,可改变 - 一个promise 在resolve或者reject前都处于这个状态- 可以通过resolve ->fufilled状态- 可以通过reject -> rejected 状态
fufilled- 最终态,不可改变 - 一个promise被resolve后会变成这个状态- 必须拥有一个value值
rejected- 最终态,不可变 - 一个promise被reject后会变成这个状态- 必须拥有一个reason

then

Promise 应该提供一个then方法,用来访问最终结果,无论的value还是reason.

promise.then(onFulfilled,onRejected)

1.参数要求

  • onFufilled用于必须是函数类型,如果不是函数,应该被忽略
  • onRejected 必须是函数类型,如果不是函数,应该被忽略

2.onFufilled特性

  • 在promise变成fufilled时,应该调用onFufilled ,参数是value
  • 在promise变成fufilled之前,不应该被调用
  • 只能被调用一次(实现的时候需要一个变量来限制执行次数)

3.onRejected 特性

  • 在promise变成rejected时,应该调用onRejected, 参数是reason
  • 在promise变成rejected之前,不应该被调用
  • 只能被调用一次(实现的时候需要一个变量来限制执行次数)

4.onFulfilled和onRejected应该是微任务的调用

可以使用queueMicrotask来实现微任务的调用(存在浏览器兼容问题)

5.then方法可以被调用多次

  • promise状态变成fufilled时,调用回调onFufilled,需要按照then的注册顺序执行,所以用数组结构来存多个onFulfilled的回调
  • promise状态变成rejected时,调用回调onRejected,需要按照then的注册顺序执行,所以用数组结构来存多个onRejected的回调

6.返回值

then 应该放回一个promise

promise2 = promise1.then(onFufilled,onRejected);
  • onFufilled 或onRejected执行的结果为x, 调用resolvePromise
  • onFufilled或onRejected执行时抛出异常e, promise2需要被reject
  • onFufilled不是一个函数,promise2以promise1的value,触发fulfilled
  • onRejected不是一个函数,promise2以promise1的reason触发rejected

7.resolvePromise

/**
 *手写代码
 const promse  = new Promise((resolve, reject) => {
    //同步执行, 宏任务
}).then(onfulfilled,onrejected).catch()
 promse.then();
 promse.then();
 */const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
​
class MyPromise {
    fufilled_callback_list = [];
    rejected_callback_list = []
    _status = PENDING;
​
    constructor(fn) {
        this.status = PENDING;
        this.value = null;
        this.reason = null;
        try {
            fn(this.resolve.bind(this), this.reject.bind(this));
        } catch (e) {
            this.reject(e);
        }
​
    }
​
    resolve(value) {
        if (this.status === PENDING) {
            this.status = FULFILLED;
            this.value = value;
        }
    }
​
    reject(reason) {
        if (this.status === PENDING) {
            this.status = REJECTED;
            this.reason = reason;
        }
    }
​
    get status() {
        return this._status;
    }
​
    set status(newValue) {
        this._status = newValue;
        if (newValue === FULFILLED) {
            this.fufilled_callback_list.forEach(fn => {
                fn(this.value)
            })
        } else if (newValue === REJECTED) {
​
            this.rejected_callback_list.forEach(fn => {
                fn(this.reason);
            })
​
        }
​
    }
​
​
    then(onFufilled, onRejected) {
        const realOnFulFilled = typeof onFufilled === 'function' ? onFufilled : value => {
            return value;//透传
        }
        const realOnRejected = typeof onRejected === 'function' ? onRejected : reason => {
            throw reason;//透传
        }
        const promise2 = new MyPromise((resolve, reject) => {
            const fulfilledMicrotask = () => {
                queueMicrotask(() => {
                    try {
                        //如果onFufilled 或者onRejected返回一个值x,则运行resolvePromise方法
                        //把上一次then执行的结果,放到下一次then里面执行
                        const x = realOnFulFilled(this.value);
                        this.resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e)
                    }
                })
            };
            const rejectedMicrotask = () => {
                queueMicrotask(() => {
                    try {
                        const x = realOnRejected(this.reason);
                        this.resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                })
            };
​
            switch (this.status) {
                case FULFILLED: {
                    fulfilledMicrotask();
                    break;
                }
                case REJECTED: {
                    rejectedMicrotask();
                    break;
                }
                case PENDING: {//当状态还是pending时,存进数组,放到setter里面去执行
                    this.fufilled_callback_list.push(fulfilledMicrotask);
                    this.rejected_callback_list.push(rejectedMicrotask);
                }
            }
        });
        return promise2
    }
    
    //返回的是 promise, 所以它们可以被链式调用。
    catch(onRejected) {
        return this.then(null, onRejected);
    }
​
    resolvePromise(promise2, x, resolve, reject) {
        //1.如果newPromise 和x指向同一个对象,就报错,防止死循环
        if (promise2 === x) {
            return reject(new TypeError('The promise and return value are the same'));
        }
​
        //2如果x为Promise ,则使newPromise 接受x的状态
        //也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y,使用微任务
        if (x instanceof MyPromise) {
            queueMicrotask(() => {
                x.then((y) => {
                    this.resolvePromise(promise2, y, resolve, reject);
                }, reject)
            })
            //3. 如果x为对象,或者函数
        }
        else if (typeof x === 'object' || this.isFnction(x)) {
            if (x === null) {
                //3.1null也会被判断为对象
                return resolve(x);
            }
            let then = null;
            try {
                then = x.then;
            } catch (e) {
                return reject(e)
            }
            //3.2 then 是函数
            if (this.isFnction(then)) {
                let called = false;
                //将x作为函数的作用域this调用
                //传递两个回调函数作为参数,第一个参数resolvePromise,
                //二 rejectPromise
                try {
                    then.call(x,
                        //resolvePromise
                        (y) => {
                            //需要一个变量called来保证只调用一次
                            if (called) return;
                            called = true;
                            this.resolvePromise(promise2, y, resolve, reject)
                        },
                        //rejectPromise
                        (r) => {
                            if (called) return;
                            called = true;
                            reject(r)
                        }
                    )
                } catch (e) {
                    //如果then 方法抛出异常e
                    if (called) return;
                    //否则拒绝
                    reject(e);
                }
            } else {
                //then不是函数,以x为参数执行promise
                resolve(x);
            }
        }
        else {
            //4 x不为对象,函数,
            resolve(x);
        }
​
    }
​
    isFnction(param) {
        return typeof param === 'function';
    }
}
//测试
const test = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve(111);
    }, 1000);
}).then(console.log);
​
console.log(test);
​
setTimeout(() => {
    console.log(test);
}, 2000)

添加resolve, reject, race静态方法

 //添加静态的resolve对象,把现有的对象转换为Promise对象,状态为fufilled
    static resolve(value){
        if(value instanceof MyPromise){
            return value;
        }
        return new MyPromise((resolve)=>{
            resolve(value);
        })
    }
    //静态的reject对象,状态为rejected
    static reject(reason) {
        return new MyPromise((resolve,reject)=>{
            reject(reason);
        })
    }
    //静态race, 多个Promise实例,包装成一个,只要有一个状态改变,就是总的改变,返回值传给p的回调函数
    static race(promiseList){
        return new MyPromise((resolve,reject)=>{
            const length = promiseList.length;
            if (length === 0) {
                return resolve();
            }else {
                for(let i=0;i<length;i++){
                    MyPromise.resolve(promiseList[i]).then(
                        (value)=>{
                            return resolve(value);
                        },
                        (reason)=>{
                            return reject(reason);
                        }
                    )
                }
            }
        })
    }
​
}
​
const test = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve(111);
    }, 1000);
}).then(console.log);
​
console.log(test);
​
setTimeout(() => {
    console.log(test);
}, 2000)
​
const test2 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve(222);
    }, 2000);
});
const test3 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve(333);
    }, 3000);
});
MyPromise.race([test2, test3]).then(console.log);

补充知识点

描述

Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值。

一个 Promise 对象代表一个在这个 promise 被创建出来时不一定已知的值。它让您能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来。 这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者。

待定状态的 Promise 对象要么会通过一个值被兑现(fulfilled) ,要么会通过一个原因(错误)被拒绝(rejected) 。当这些情况之一发生时,我们用 promise 的 then 方法排列起来的相关处理程序就会被调用。如果 promise 在一个相应的处理程序被绑定时就已经被兑现或被拒绝了,那么这个处理程序就会被调用,因此在完成异步操作和绑定处理方法之间不会存在竞争状态。

我们可以用 promise.then()promise.catch()promise.finally() 这些方法将进一步的操作与一个变为已敲定状态的 promise 关联起来。这些方法还会返回一个新生成的 promise 对象,这个对象可以被非强制性的用来做链式调用,

链式调用把上一次的值,传到下一次使用

const myPromise = new Promise(((resolve, reject) => {
    //同步执行
    console.log('star1')
    resolve(1)
})).then(data => {
    //异步,微任务
    console.log('data1', data)
    return data + 1;
}, error => {
    return 'error'
}).then(data => {
    console.log('data11:', data)
    return data + 1;
}, error => {
    return 'error'
});
//结果
star1
statr2
data1 1
data11: 2
myPromise1 Promise { 3 }

只接收resolve的值, 体会then异步执行,微任务

const myPromise = new Promise(((resolve, reject) => {
    console.log('star1')
    resolve(1)
})).then(data => {
    console.log('data1', data)
    return data + 1;
}, error => {
    return 'error'
}).then(data => {
    console.log('data11:', data)
    return data + 1;
}, error => {
    return 'error'
});
​
console.log('statr2')
​
setTimeout(() => {//宏任务
    console.log('myPromise1', myPromise)
}, 3000);
​
console.log('------------------------------------')
const myPromise1 = new Promise(((resolve, reject) => {
    resolve(1)
}))
myPromise1.then(data => {
    console.log('data2', data)
    return data + 1;
}, error => {
    return 'error'
})
myPromise1.then(data => {
    console.log('data22:', data)
    return data + 1;
}, error => {
    return 'error'
});
​
setTimeout(() => {//宏任务
    console.log('myPromise2', myPromise1)
}, 6000);
//结果
star1
statr2
------------------------------------
data1 1
data2 1
data22: 1
data11: 2
myPromise1 Promise { 3 }
myPromise2 Promise { 1 }
​

来抛个异常,看看then 运行到哪里

const myPromise = new Promise(((resolve, reject) => {
    console.log('star1')
    resolve(1)
})).then(data => {
    console.log('data1', data)
    throw new TypeError('error1')
}, error => {
    console.log('ending', error)
}).then(data => {
    console.log('data11:', data)
    return data + 1;
}, error => {
   console.log('run here')
});
//结果
star1
data1 1
run here

程序跑到了run here ,说明 在第一个then的onFulfilled抛出的异常被第一个then返回的promse的onRejected 回调接收了

对应代码,因为设置onRejected 回调

如不设置onRejected回调,设置catch

const myPromise = new Promise(((resolve, reject) => {
    console.log('star1')
    resolve(1)
})).then(data => {
    console.log('data1', data)
    throw new TypeError('error1')
}).then(data => {
    console.log('data11:', data)
    return data + 1;
}).then(data => {
    console.log('data2:', data)
    return data + 1;
}).catch(error => {
    console.log('end', error)
});
//结果
star1
data1 1
end TypeError: error1
    at processTicksAndRejections (node:internal/process/task_queues:96:5)

看到,error 最终会被catch捕获,有一个抛出错误,之后的onFufilled都不会执行,因为异常被reject传递下去

如果中间添加一个onRejected

const myPromise = new Promise(((resolve, reject) => {
    console.log('star1')
    resolve(1)
})).then(data => {
    console.log('data1', data)
    throw new TypeError('error1')
}).then(data => {
    console.log('data11:', data)
    return data + 1;
}).then(data => {
    console.log('data2:', data)
    return data + 1;
}, error => {
    console.log('是否会调用这里', error);
}).catch(error => {
    console.log('end', error)
});
//结果
star1
data1 1
是否会调用这里 TypeError: error1
    at

明确体现了链式调用

Promise.All

这个方法返回一个新的promise对象,该promise对象在iterable参数对象里所有的promise对象都成功的时候才会触发成功,一旦有任何一个iterable里面的promise对象失败则立即触发该promise对象的失败。这个新的promise对象在触发成功状态以后,会把一个包含iterable里所有promise返回值的数组作为成功回调的返回值,顺序跟iterable的顺序保持一致;如果这个新的promise对象触发了失败状态,它会把iterable里第一个触发失败的promise对象的错误信息作为它的失败错误信息。Promise.all方法常被用于处理多个promise对象的状态集合。

例子

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});
​
Promise.all([promise1, promise2, promise3]).then((values) => {
  console.log(values);
});
// expected output: Array [3, 42, "foo"]

参数与返回

参数中包含非 promise 值,这些值将被忽略,但仍然会被放在返回数组中

  • 空的可迭代对象------> 已完成fufilled 的Promise
  • 不包含任何promise -------> 异步完成的Promise
  • 其他情况 ---------> pending 的Promise

任何一个promise失败,结果为失败, Promise.all 异步地将失败的那个结果给失败状态的回调函数,而不管其它 promise 是否完成。

任何情况下,Promise.all 返回的promise的完成状态的结果是一个数组

fufilled例子

// we are passing as argument an array of promises that are already resolved,
// to trigger Promise.all as soon as possible
var resolvedPromisesArray = [Promise.resolve(33), Promise.resolve(44)];
​
var p = Promise.all(resolvedPromisesArray);
// immediately logging the value of p
console.log(p);
​
// using setTimeout we can execute code after the stack is empty
setTimeout(function(){
    console.log('the stack is now empty');
    console.log(p);
});
​
// logs, in order:
// Promise { <state>: "pending" }
// the stack is now empty
// Promise { <state>: "fulfilled", <value>: Array[2] }

rejected例子

var p = Promise.all([]); // will be immediately resolved
var p2 = Promise.all([1337, "hi"]); // non-promise values will be ignored, but the evaluation will be done asynchronously
console.log(p);
console.log(p2)
setTimeout(function(){
    console.log('the stack is now empty');
    console.log(p2);
});
​
// logs
// Promise { <state>: "fulfilled", <value>: Array[0] }
// Promise { <state>: "pending" }
// the stack is now empty
// Promise { <state>: "fulfilled", <value>: Array[2] }

手写一个Promise.All

Promise.myAll= promises=>{
    return new Promise((resolve, reject) => {
        if(promises.length===0){
          resolve([])
        }
        let count=0;//计数是否结束
        const result =[];
        const len = promises.length;
        promises.forEach((p,i)=>{
            //先初始化,判断是不是Promise
            Promise.resolve(p).then(res=>{
                count++;
                result[i]=res;
                if (count === len) {
                    resolve(result);
                }
            }).catch(e=>{
                reject(e);//一个抛错,就立马返回
            })
        })

    })
};

// 测试一下
const p1 = Promise.resolve(1)
const p2 = new Promise((resolve) => {
    setTimeout(() => resolve(2), 1000)
})
const p3 = new Promise((resolve) => {
    setTimeout(() => resolve(3), 3000)
})

const p4 = Promise.reject('err4')
const p5 = Promise.reject('err5')
// 1. 所有的Promise都成功了
const p11 = Promise.myAll([ p1, p2, p3 ])
    .then(console.log) // [ 1, 2, 3 ]
    .catch(console.log)

// 2. 有一个Promise失败了
const p12 = Promise.myAll([ p1, p2, p4 ])
    .then(console.log)
    .catch(console.log) // err4

// 3. 有两个Promise失败了,可以看到最终输出的是err4,第一个失败的返回值
const p13 = Promise.myAll([ p1, p4, p5 ])
    .then(console.log)
    .catch(console.log) // err4
// 与原生的Promise.all返回是一致的

问题

为什么promise resolve了一个value, 最后输出的value值确是undefined

const test = new MPromise((resolve, reject) => {
    setTimeout(() => {
        resolve(111);
    }, 1000);
}).then((value) => {
    console.log('then');
    //没有return 
});
​
setTimeout(() => {
    console.log(test);
}, 3000)

答:
因为现在这种写法, 相当于在.thenreturn undefined, 所以最后的value是undefined. 
如果显式return一个值, 就不是undefined了;比如return value.

then返回的是一个新Promise, 那么原来promise实现的时候, 用数组来存回调函数有什么意义?

这个问题提出的时候, 应该是有一个假定条件, 就是链式调用的时候. 
​
这个时候, 每一个.then返回的都是一个新promise, 所以每次回调数组FULFILLED_CALLBACK_LIST都是空数组. 
​
针对这种情况, 确实用数组来存储回调没意义, 完全可以就用一个变量来存储。
```js
const test = new MPromise((resolve, reject) => {
    setTimeout(() => {
          resolve(111);
      }, 1000);
  }).then((value) => {
      
  }).then(() => {
  
  })
```
但是还有一种promise使用的方式, 这种情况下, promise实例是同一个, 数组的存在就有了意义
​
```js
const test = new MPromise((resolve, reject) => {
    setTimeout(() => {
        resolve(111);
    }, 1000);
})
​
test.then(() => {});
test.then(() => {});
test.then(() => {});
test.then(() => {});
```

为什么我在catch的回调里, 打印promise, 显示状态是pending

​
​
```js
const test = new MPromise((resolve, reject) => {
    setTimeout(() => {
        reject(111);
    }, 1000);
}).catch((reason) => {
    console.log('报错' + reason);
    console.log(test)
});
​
setTimeout(() => {
    console.log(test);
}, 3000)
​
```
​
1. catch 函数会返回一个新的promise, 而test就是这个新promise
2. catch 的回调里, 打印promise的时候, 整个回调还并没有执行完成(所以此时的状态是pending), 只有当整个回调完成了, 才会更改状态
3. catch 的回调函数, 如果成功执行完成了, 会改变这个新Promise的状态为fulfilled

执行顺序问题

Promise.resolve()
    .then(async () => {
        console.log(0);
        setTimeout(() => {
            console.log('宏任务');
        }, 0);
        return Promise.resolve(4);
    })
    .then((res) => {
        console.log(res);
    });
//0,4,宏任务
Promise.resolve().then(() => {
        console.log(1);
    })
    .then(() => {
        console.log(2);
    })
    .then(() => {
        console.log(3);
    })
    .then(() => {
        console.log(5);
    })
    .then(() => {
        console.log(6);
    })
    .then(() => {
        console.log(7);
    })
​
//1,2,3,4,5,6,7