Promise完全解读
Posted xizugogo
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Promise完全解读相关的知识,希望对你有一定的参考价值。
Promise简介
是一个保存了异步事件未来执行结果的对象。它不是一个新技术,而是一种可以优化异步编程编码风格的规范。最早诞生于社区,用于解决javascript代码中回调嵌套层级过多的问题和错误处理逻辑堆砌的问题。使用Promise对象封装异步操作,可以让代码变得直观、线性、优雅。
Promise基本使用
用Promise封装Ajax请求
//先写一个原始的Ajax请求
let xhr = new XMLHttpRequest()
function resolve(v){console.log(v);}
function reject(e){console.log(e);}
xhr.onerror = function(e){
reject(e)
}
xhr.ontimeout = function(e){
reject(e)
}
xhr.onreadystatechange = function(){
if(xhr.readyState===4){
if(xhr.status===200){
resolve(xhr.response)
}
}
}
xhr.open(\'Get\',\'https://wwww.google.com\',true)
xhr.send()
// 第一版:利用Promise封装ajax
let p = new Promise((resolve,reject)=>{
let xhr = new XMLHttpRequest()
xhr.onerror = function(e){
reject(e)
}
xhr.ontimeout = function(e){
reject(e)
}
xhr.onreadystatechange = function(){
if(xhr.readyState===4){
if(xhr.status===200){
resolve(xhr.response)
}
}
}
xhr.open(\'Get\',\'https://wwww.google.com\',true)
xhr.send()
});
p.then(v=>{
console.log("我是成功时注册的回调");
},e=>{
console.log("我是失败时注册的回调");
})
// 第二版 支持传参、封装了Promise创建的细节
function Xpromise(request){
function executor(request,resolve,reject){
let xhr = new XMLHttpRequest()
xhr.onerror = function(e){
reject(e)
}
xhr.ontimeout = function(e){
reject(e)
}
xhr.onreadystatechange = function(){
if(xhr.readyState===4){
if(xhr.status===200){
resolve(xhr.response)
}
}
}
xhr.open(\'Get\',request.url,true)
xhr.send()
}
renturn new Promise(executor);
}
let x1 = Xpromise(makeRequest(\'https://wwww.google.com\')).then(v=>{
console.log("我是成功时注册的回调");
},e=>{
console.log("我是失败时注册的回调");
})
另外,Promise还提供了一系列好用的API,如静态resolve()、all()、race()方法等。
实现原理概述
Promise用回调函数延迟绑定、回调函数onResolve返回值穿透机制解决回调嵌套层级过多的问题;使用错误冒泡机制简化了错误处理逻辑堆砌的问题。
手工实现一个Promise
第一版
// 第一点:Promise是一个类
class MyPromise {
// 第二点:Promised构造函数的参数是一个函数;
constructor(fn) {
if (typeof fn !== "function") {
throw new Error("promise的构造函数参数应该为函数类型")
}
// 第三点:Promise的内部状态有三个,Promise对象具有值
this._status = PENDING;
this._value = undefined;
// 第五点:new Promise(fn)时,就需要执行fn做业务逻辑,故构造函数里就要调用fn函数。此处内部函数_resolve和_reject会被调用用于追踪Promise的内部状态
try {
//注意用try-catch包住
fn(this._resolve.bind(this), this._reject.bind(this));
} catch (err) {
this._reject(err)
}
}
// 第四点:定义MyPromise状态翻转时,要执行的内部函数
_resolve(val){
if (this._status !== this.PENDING) return //这段代码体现了Promise的状态翻转:只能是P->F或者是P->R
this._status = FULLFILLED;
this._value = val;
};
_reject(err){
if (this._status !== this.PENDING) return
this._status = REJECTED;
this._value = err;
};
}
第二版
class MyPromise {
constructor(fn) {
if (typeof fn !== "function") {
throw new Error("myPromise的构造函数参数应该为函数类型")
}
this._status = PENDING;
this._value = undefined;
//特性2-新增定义两个回调函数数组
this.fulfilledQueue = [];
this.rejectedQueue = [];
try {
fn(this._resolve.bind(this), this._reject.bind(this));
} catch (err) {
this._reject(err)
}
}
_resolve(val){
if (this._status !== this.PENDING) return
// 特性4:注册的回调函数在Promise状态翻转时会执行,执行的方式是循环从队列里面取出回调执行
// 定义run函数
run = (value)=>{
this._status = FULLFILLED;
this._value = val;
let ck;
while(ck = this.fulfilledQueue.shift()){
ck(value);
}
}
// 特性5:run()函数的执行,这里非常关键。要把run放进setTimeOut。为什么?
// 因为执行_resolve()函数时,then()可能还没执行,所以为了让then()中的回调、包括链式调用的then()的回调添加到fulfilledQueue中,
// 需要延迟执行run()。实际上这里用setTimeout性能差,实际中采用微任务的方式实现
setTimeout(run,0)
//run();
};
_reject(err){
if (this._status !== this.PENDING) return
run = (error)=>{
this._status = this.REJECTED;
this._value = err;
let ck;
while(ck = this.rejectedQueue.shift()){
ck(error)
}
}
setTimeout(run,0);
};
// 最重要的then函数:
// 特性1-用于注册回调,then()函数有两个参数,都是可选的,如果参数不是函数将会被忽略
// 同一个Promise可以多次注册then(),特性2-所以Promise内部要维护两个数组,分别存储then上注册的成功回调和失败回调);
// then()支持链式调用,特性3-之所以支持是因为其返回值是一个新的Promise,此处要完成回调函数onFulfilled穿透机制的实现、错误冒泡机制的实现
//特性1-回调函数的注册:故为它传递两个回调函数占位
then(onFulfilled,onRejected){
const {_status, _value} = this;
//特性3-返回一个新的promise
return new MyPromise((onFulfilledNext, onRejectedNext)=>{
let fulfilled = value => {
try{
if(typeof onFulfilled != "function"){
//如果 onFulfilled 或 onRejected 不是函数,onFulfilled 或 onRejected 被忽略
onFulfilledNext(value)
}else{
//如果 onFulfilled 或者 onRejected 返回一个值 res
let res = onFulfilled(value)
if(res instanceof MyPromise){
//若 res 为 Promise ,这时后一个回调函数,就会等待该 Promise 对象(即res )的状态发生变化,才会被调用,并且新的 Promise 状态和 res 的状态相同
res.then(onFulfilledNext, onRejectedNext)
}else{
//若 res 不为 Promise ,则使res 直接作为新返回的 Promise 对象的值
onFulfilledNext(res)
}
}
}catch(err){
onRejectedNext(err)
}
}
let rejected = error => {
try{
if(typeof onRejected != "function"){
onRejectedNext(error)
}else{
let res = onRejectedNext(error)
if(res instanceof MyPromise){
res.then(onFulfilledNext, onRejectedNext)
}else{
onFulfilledNext(res);
}
}
}catch(err){
onRejectedNext(err)
}
}
switch(_status){
case PENDING:
//在 promise 状态改变前, onFulfilled或者onRejected不可被调用
this._fulfilledQueue.push(onFulfilled)
this._rejectedQueue.push(onRejected)
break
case FULFILLED:
//onFulfilled调用次数只有一次,fulfilled对onFulFilled进行包装。why need 包装?因为onFulFilled可能是函数、可能不是,如果是函数,返回值可能是Promise可能不是
fulfilled(_value)
break
case REJECTED:
//onRejected调用次数只有一次
rejected(_value)
break
}
})
}
}
第三版
class MyPromise {
constructor(handle) {
if (!isFunction(handle)) {
throw new Error(\'MyPromise must accept a function as a parameter\')
}
this._status = PENDING
this._value = undefined
this._fulfilledQueues = []
this._rejectedQueues = []
try {
handle(this._resolve.bind(this), this._reject.bind(this))
} catch (err) {
this._reject(err)
}
}
// 添加resovle时执行的函数
_resolve(val) {
const run = () => {
if (this._status !== PENDING) return
const runFulfilled = (value) => {
let cb;
while (cb = this._fulfilledQueues.shift()) {
cb(value)
}
}
const runRejected = (error) => {
let cb;
while (cb = this._rejectedQueues.shift()) {
cb(error)
}
}
/*
如果resolve的参数为Promise对象,则必须等待该Promise对象状态改变后,
当前Promsie的状态才会改变,且状态取决于参数Promsie对象的状态,
因此这里进行逻辑区分
*/
if (val instanceof MyPromise) {
val.then(value => {
this._value = value
this._status = FULFILLED
runFulfilled(value)
}, err => {
this._value = err
this._status = REJECTED
runRejected(err)
})
} else {
this._value = val
this._status = FULFILLED
runFulfilled(val)
}
}
setTimeout(run, 0)
}
_reject(err) {
if (this._status !== PENDING) return
const run = () => {
this._status = REJECTED
this._value = err
let cb;
while (cb = this._rejectedQueues.shift()) {
cb(err)
}
}
setTimeout(run, 0)
}
then(onFulfilled, onRejected) {
const { _value, _status } = this
return new MyPromise((onFulfilledNext, onRejectedNext) => {
let fulfilled = value => {
try {
if (!isFunction(onFulfilled)) {
onFulfilledNext(value)
} else {
let res = onFulfilled(value);
if (res instanceof MyPromise) {
res.then(onFulfilledNext, onRejectedNext)
} else {
onFulfilledNext(res)
}
}
} catch (err) {
onRejectedNext(err)
}
}
let rejected = error => {
try {
if (!isFunction(onRejected)) {
onRejectedNext(error)
} else {
let res = onRejected(error);
if (res instanceof MyPromise) {
res.then(onFulfilledNext, onRejectedNext)
} else {
onFulfilledNext(res)
}
}
} catch (err) {
onRejectedNext(err)
}
}
switch (_status) {
case PENDING:
this._fulfilledQueues.push(fulfilled)
this._rejectedQueues.push(rejected)
break
case FULFILLED:
fulfilled(_value)
break
case REJECTED:
rejected(_value)
break
}
})
}
}
第四版 finally
class MyPromise {
constructor(handle) {
if (!isFunction(handle)) {
throw new Error(\'MyPromise must accept a function as a parameter\')
}
this._status = PENDING
this._value = undefined
this._fulfilledQueues = []
this._rejectedQueues = []
try {
handle(this._resolve.bind(this), this._reject.bind(this))
} catch (err) {
this._reject(err)
}
}
_resolve(val) {
const run = () => {
if (this._status !== PENDING) return
const runFulfilled = (value) => {
let cb;
while (cb = this._fulfilledQueues.shift()) {
cb(value)
}
}
const runRejected = (error) => {
let cb;
while (cb = this._rejectedQueues.shift()) {
cb(error)
}
}
if (val instanceof MyPromise) {
val.then(value => {
this._value = value
this._status = FULFILLED
runFulfilled(value)
}, err => {
this._value = err
this._status = REJECTED
runRejected(err)
})
} else {
this._value = val
this._status = FULFILLED
runFulfilled(val)
}
}
setTimeout(run, 0)
}
_reject(err) {
if (this._status !== PENDING) return
const run = () => {
this._status = REJECTED
this._value = err
let cb;
while (cb = this._rejectedQueues.shift()) {
cb(err)
}
}
setTimeout(run, 0)
}
then(onFulfilled, onRejected) {
const { _value, _status } = this
return new MyPromise((onFulfilledNext, onRejectedNext) => {
let fulfilled = value => {
try {
if (!isFunction(onFulfilled)) {
onFulfilledNext(value)
} else {
let res = onFulfilled(value);
if (res instanceof MyPromise) {
res.then(onFulfilledNext, onRejectedNext)
} else {
onFulfilledNext(res)
}
}
} catch (err) {
onRejectedNext(err)
}
}
let rejected = error => {
try {
if (!isFunction(onRejected)) {
onRejectedNext(error)
} else {
let res = onRejected(error);
if (res instanceof MyPromise) {
res.then(onFulfilledNext, onRejectedNext)
} else {
onFulfilledNext(res)
}
}
} catch (err) {
onRejectedNext(err)
}
}
switch (_status) {
case PENDING:
this._fulfilledQueues.push(fulfilled)
this._rejectedQueues.push(rejected)
break
case FULFILLED:
fulfilled(_value)
break
case REJECTED:
rejected(_value)
break
}
})
}
// 添加catch方法
catch(onRejected) {
return this.then(undefined, onRejected)
}
// 添加静态resolve方法
static resolve(value) {
// 如果参数是MyPromise实例,直接返回这个实例
if (value instanceof MyPromise) return value
return new MyPromise(resolve => resolve(value))
}
// 添加静态reject方法
static reject(value) {
return new MyPromise((resolve, reject) => reject(value))
}
// 添加静态all方法
static all(list) {
return new MyPromise((resolve, reject) => {
/**
* 返回值的集合
*/
let values = []
let count = 0
for (let [i, p] of list.entries()) {
// 数组参数如果不是MyPromise实例,先调用MyPromise.resolve
this.resolve(p).then(res => {
values[i] = res
count++
// 所有状态都变成fulfilled时返回的MyPromise状态就变成fulfilled
if (count === list.length) resolve(values)
}, err => {
// 有一个被rejected时返回的MyPromise状态就变成rejected
reject(err)
})
}
})
}
// 添加静态race方法
static race(list) {
return new MyPromise((resolve, reject) => {
for (let p of list) {
// 只要有一个实例率先改变状态,新的MyPromise的状态就跟着改变
this.resolve(p).then(res => {
resolve(res)
}, err => {
reject(err)
})
}
})
}
finally(cb) {
return this.then(
value => MyPromise.resolve(cb()).then(() => value),
reason => MyPromise.resolve(cb()).then(() => { throw reason })
);
}
}
其它问题
1. Promise与宏观任务、async函数等执行顺序问题
Promise是微任务的一种实现,给出如下的代码,分析其输出顺序
//题目1
async function a1 () {
console.log(\'a1 start\')
await a2()
console.log(\'a1 end\')
}
async function a2 () {
console.log(\'a2\')
}
console.log(\'script start\')
setTimeout(() => {
console.log(\'setTimeout\')
}, 0)
Promise.resolve().then(() => {
console.log(\'promise1\')
})
a1()
let promise2 = new Promise((resolve) => {
resolve(\'promise2.then\')
console.log(\'promise2\')
})
promise2.then((res) => {
console.log(res)
Promise.resolve().then(() => {
console.log(\'promise3\')
})
})
console.log(\'script end\')
//题目2
async function async1() {
console.log(\'async1 start\');
await async2();
await async3()
console.log(\'async1 end\');
}
async function async2() {
console.log(\'async2\');
}
async function async3() {
console.log(\'async3\');
}
console.log(\'script start\');
setTimeout(function() {
console.log(\'setTimeout\');
}, 0)
async1();
new Promise(function(resolve) {
console.log(\'promise1\');
resolve();
}).then(function() {
console.log(\'promise2\');
});
console.log(\'script end\');
//题目3
async function async1(){
console.log(\'async1 start\')
await async2()
console.log(\'async1 end\')
}
async function async2(){
console.log(\'async2\')
}
console.log(\'script start\')
setTimeout(function(){
console.log(\'setTimeout0\')
},0)
setTimeout(function(){
console.log(\'setTimeout3\')
},3)
setImmediate(() => console.log(\'setImmediate\'));
process.nextTick(() => console.log(\'nextTick\'));
async1();
new Promise(function(resolve){
console.log(\'promise1\')
resolve();
console.log(\'promise2\')
}).then(function(){
console.log(\'promise3\')
})
console.log(\'script end\')
答案:
题目一:script start->a1 start->a2->promise2->script end->promise1->a1 end->promise2.then->promise3->setTimeout
题目二:script start->async1 start->async2->promise1->script end->async3->promise2->async1 end->setTimeout
在浏览器console可实验
题目三:script start->async1 start->async2->promise1->promise2
->script end->nextTick->async1 end->promise3->setTimeout->setImmediate->setTimeout3
在node环境中可实验
2. 如何实现all、如何实现链式调用的
all()的实现:函数中维护了一个数组和计数器,数组的大小为初始时all函数中传递的Promise对象数量,数组存储各个Promise执行成功后resolve得到的结果,每成功一个计数器+1,直到计数器累加到数组大小时即调用resolve(value),只要有一个Promise执行到失败的回调,即全部失败。
链式调用的实现:then函数返回的依然是一个Promise,见第二版的Promise实现。
3. all中任何一个Promise出错,导致其它正确的数据也无法使用,如何解决呢?
方法1:使用allSettled替代;方法2:改写Promise,将reject操作换成是resolve(new Error("自定义的错误"));方法3:引入第三方库promise-transaction
4.Promise真的好用吗,它存在什么问题?
问题1:没有提供中途取消的机制;问题2:必须要设置回调,否则内部错误无法在外部反映出来;问题3:使用时依然存在大量Promise的api,逻辑不清晰。
以上是关于Promise完全解读的主要内容,如果未能解决你的问题,请参考以下文章
VSCode自定义代码片段12——JavaScript的Promise对象