【es6】谈谈Promise

学习vue写项目已经很久了,在项目中有许多地方会使用到Promise,无论前后端的接口通信,还是各种第三方的组件库或是插件,或多或少都会发现Promise的影子,所以今天简单梳理一下关于Promise的知识体系。

目录

一.什么是Promise

二.关于回调函数

1.1. 同步调用的回调函数

1.2. 异步调用的回调函数

三.关于js的错误处理

1.1. Error:所有错误的父类型

1.2. ReferenceError:引用的变量不存在

1.3. TypeError:类型错误

1.4. RangeError:数值不在其所允许的范围内

1.5. SyntaxError:语法错误

2.1. 捕获错误:try...catch

2.2. 抛出错误:throw error

四.实现Promise封装XMLHttpRequest

五.Promise的优点

1.指定回调函数的方式更加灵活

2.支持链式调用,解决回调地狱

六.Promise API

0.Promise构造函数

1.Promise.prototype.then

2.Promise.prototype.catch

3.Promise.resolve

4.Promise.reject

5.Promise.all

6.Promise.race

7.Promise.allSettled

七.Promise深入

1.如何改变promise的状态?

2. 一个Promise指定了多个成功或失败的回调,是否都会调用?

3.改变Promise状态和指定回调的谁先谁后?

4.promise.then返回的新Promise的状态由什么决定?

5.Promise异常穿透

6.中断Promise链

7.如何保证Promise.all始终返回成功的状态

八.自定义Promise

1.定义Promise构造函数

1)定义构造函数的属性

2)调用执行器,捕获异常

3)修改Promise的状态

2.定义then方法

1) 判断当前的Promise实例的状态 

2) 确定then方法返回的Promise状态

3) 指定回调函数的默认值

3.定义catch方法

4.定义resolve方法

5.定义reject方法

6.定义all方法

7.定义race方法

8.定义allSettled方法

九.js宏队列和微队列


一.什么是Promise

取代纯回调形式进行异步编程的解决方案(抽象);它是一个构造函数(语法);封装一个异步操作并可以获取结果(功能);

Promise存在三个状态,pending未确定的,resolved成功,rejected失败

Promise状态的改变,pending变为resolved,以及pending变为rejected。需要注意的是,一个Promise对象状态只能改变一次

二.关于回调函数

在正式聊Promise之前,需要回顾下回调函数的概念,因为Promise编程中会出现大量的回调函数。回调函数,区别于普通函数,需要满足几个条件:

1.是开发者自己定义的

2. 不会主动调用

3.最终会被执行

回调函数通常分为两类,同步回调和异步回调:

1.1. 同步调用的回调函数

一上来就执行,不会放入队列中

const arr = [1,2,3];
arr.forEach(item => {
    console.log(item); // 同步回调函数,先打印
)
console.log('forEach执行完毕');  // 后打印

1.2. 异步调用的回调函数

会放入队列中在同步代码执行后执行

setTimeout(() => {
    console.log('setTimeout callback');  // 异步回调函数,后执行
});
console.log('setTimeout after');   // 先执行

三.关于js的错误处理

Promise中会出现成功或失败的回调,在失败的回调中,会使用js的错误处理失败的结果,在这里进行一个总结。

js常见的错误类型:

1.1. Error所有错误的父类型

1.2. ReferenceError引用的变量不存在

 在浏览器控制台输入如下代码

console.log(a)

会在控制台出现如下的报错,那是由于你引用了一个不存在的变量a导致的 

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA4KaP546W5Ly0,size_20,color_FFFFFF,t_70,g_se,x_16

1.3. TypeError:类型错误

 在浏览器控制台输入如下代码

let b = null;
console.log(b.xxx);

 需要在b上读取xxx属性的前提是b必须是一个对象,但b是null非对象,报类型错误

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA4KaP546W5Ly0,size_20,color_FFFFFF,t_70,g_se,x_16

1.4. RangeError:数值不在其所允许的范围内

function fn() {
    fn();  // 函数递归调用
}

fn();  

 在fn函数内递归调用自己,但需要一个调用次数的限制,由没有定义限制导致超过了限制,所以报超过范围错误

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA4KaP546W5Ly0,size_20,color_FFFFFF,t_70,g_se,x_16

1.5. SyntaxError:语法错误

const a = """";

 双引号内不能包裹双引号,这是语法错误

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA4KaP546W5Ly0,size_20,color_FFFFFF,t_70,g_se,x_16

当js代码出现错误,此时程序无法继续向下执行,此时还需要继续向下执行程序,就需要处理错误,有两种方法:

2.1. 捕获错误:try...catch

将有错误的代码放在try里面,捕获异常,在catch中获取抛出的异常对象error,它有两个属性error.message(错误的信息)error.stack(错误的信息和错误的位置,即函数调用栈记录信息)。这样之后的代码就可正常执行。

try{
    let a;
    a.xxx;
} catch(error) {
    console.log(error);
}

2.2. 抛出错误:throw error

function judgeTime() {
    if(new Date() % 2 !== 0) {
        console.log('奇数,执行');
    } else {
        throw new Error('偶数,无法执行');  // 抛出异常
    }
}

try{
    judgeTime();   // 捕获异常
} catch(error) {
    alert(error.message);
}

四.实现Promise封装XMLHttpRequest

0.使用Promise对原生XMLHttpRequest封装,以向指定接口https://api.apiopen.top/getJoke发送请求,获取响应数据;

1.实例化promise对象new Promise,对象的参数是一个函数类型的值,函数有两个形参,形参的名字自定义,通常是resolve,reject;

2.得到数据以后,可以调用形参resolve或reject函数改变promise的状态,状态改变为成功(三状态:初始化,成功,失败),当XMLHttpRequest实例的状态码为200,响应成功,调用resolve方法,预示此Promise实例返回的状态是成功的;当XMLHttpRequest实例的状态码非[200,300],此次请求的响应失败,调用reject方法,预示此Promise实例返回的状态是失败的。

3.随后调用promise对象的then方法,接受两个参数,都是函数类型的值,每个函数都有一个形参,成功的形参一般叫做value,失败的形参叫做reason;当promise对象状态为成功,即异步中调用resolve时,then方法就会执行第一个回调函数的代码;当promise对象状态为失败,即异步中调用reject时,then方法就会执行第二个回调函数的代码。

const p = new Promise((resolve,reject) => {
    //1.创建对象
    const xhr = new XMLHttpRequest();

    //2.初始化
    xhr.open('GET','https://api.apiopen.top/getJoke');

    //3.发送
    xhr.send();

    //4.绑定事件,处理相应结果
    xhr.onreadystatechange = function(){
        //判断
        /*on 当...时候
         *readystate是xhr对象中的属,表示状态,有0 1 2 3 4
            0:表示未初始化
            1:open方法调用完毕
            2:send方法调用完毕
            3:服务端返回了部分的结果
            4:服务端返回了所有的结果
            一共有五个值,总共会触发4次
         *change 改变
         */
        if (xhr.readyState == 4) {
            //判断相应状态码 200 -299 为成功的
            if (xhr.status >= 200 && xhr.status <=300) {
                //表示成功
                resolve(xhr.response);
            }else{
                //如果失败
                reject(xhr.status);
            }
        }
    }
});
//指定回调
p.then(function(value){
    // 这里的value获取的就是resolve的实参xhr.response,这里是接口的响应结果
    console.log(JSON.parse(value));  
},function(reason){
    // 这里的reason获取的就是reject的实参xhr.status,这里是响应失败的状态码
    console.error(reason);  
})

控制台输出:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA4KaP546W5Ly0,size_20,color_FFFFFF,t_70,g_se,x_16

五.Promise的优点

1.指定回调函数的方式更加灵活

不使用Promise执行异步任务,必须在启动异步任务前指定好成功和失败的回调;

使用Promise后可以先执行异步任务,不指定结果处理的回调,等异步结果返回后再指定也可以;

2.支持链式调用,解决回调地狱

   何为回调地狱?  回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调函数执行的条件。缩进严重,代码可读性差。

六.Promise API

0.Promise构造函数

构造函数需要传入一个执行器作为参数,它是同步执行的,异步操作在执行器中执行

执行器函数需要传递两个参数,resolve和reject函数:

    resolve:内部定义成功时调用;

    reject:内部定义失败时调用;

1.Promise.prototype.then

需要两个回调函数onResolved和onRejected作为参数:

    onResolved函数:成功的回调函数,当Promise状态为成功时调用 (value) => {}

    onRejected函数:失败的回调,当Promise状态为失败的时候调用  (reason) => {}

这两个回调函数都会返回一个新的Promise对象;

2.Promise.prototype.catch

当内部定义失败时候调用,它是Promise.prototype.then的语法糖,相当于then(undefined,onRejected)

const p1 = new Promise((resolve,reject) => {
    setTimeout(() => {
        let rand = Math.ceil(Math.random() * 10);
        if(rand % 2 === 0) {
            resolve(rand);
        } else {
            reject('error');
        }
    },1000) 
});

p1.then(
    (value) => {
        console.log(value);
    }
)
.catch(
    (err) => {
        console.log(err);
    }
 );

3.Promise.resolve

快速产生一个成功的Promise

如果要生成一个成功值为1的Promise可以这样写:

const p = new Promise((resolve,reject) => {
    resolve(1);
});   
p.then(value => {console.log(value)});  // 1

但以上写法比较繁琐,可以直接调用resolve方法实现,属于语法糖

const p = Promise.resolve(1);
p.then(value => {console.log(value)});  // 1

4.Promise.reject

和上面一样的道理,若要快速产生一个失败值为1的Promise,可以直接调用其静态方法reject

const p = Promise.reject(1);
p.catch(reason => {console.log(reason)});  // 1

5.Promise.all

需要传递一个数组作为参数,数组元素是多个Promise

返回结果:一个新的Promise

  • 当这些Promise存在失败的Promise时,最终返回的结果为失败
  • 这些Promise都是成功的状态时,返回结果为成功,值为一个数组,分别是这些Promise的异步返回结果,返回顺序和在all中数组元素的顺序相一致
const p1 = new Promise((resolve,reject) => {
    resolve(1);
});
const p2 = Promise.resolve(2);
const p3 = Promise.reject(3);
// const pAll = Promise.all([p1,p2,p3]);
const pAll = Promise.all([p1,p2]);
pAll.then(
    values => {
        console.log(values);  // [1,2]
    },
    reason => {
        console.log(reason);  // 3
    }
);

6.Promise.race

参数包含n各Promise的数组

返回是一个新的Promise,第一个完成的Promise的结果状态就是最终的结果状态

const p1 = new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve(1);
    },1000);
});
const p2 = Promise.resolve(2);
const p3 = Promise.reject(3);
const race = Promise.race([p1,p2,p3]);
race.then(
    value => {
        console.log(value);  // 2
    },
    reason => {
        console.log(reason);  
    }
);

虽然race的参数是p1,p2,p3但是p1存在一秒的延时,所以p2是第一个完成的Promise,最终race的结果就是p2的结果。

7.Promise.allSettled

Promise.all方法resolve的条件在于参数数组中的所有Promise实例都需要resolve才可以,这样显然在一些业务中不适用的,假如一个模块需要显示三部分内容,每一部分内容都有一个返回Promise实例的接口,如果使用Promise.all需要三个接口都成功返回数据才可以,如果有一个接口挂掉了,则另外两个接口返回的数据不能被获取到,因为此时已经进入到了catch方法,无法在成功回调的函数里面操作数据,渲染界面等。

此时Promise.allSettled便派上用场了。无论参数实例是否reject,最终Promise.allSettled内部都会resolve,只不过会添加一个状态status来记录对应的参数实例是否执行成功。我们可以依据这个状态去过滤掉rejected的数据,只操作fulfilled的数据,就会得到我们想要的业务逻辑了。

var promise1 = new Promise(function(resolve,reject){
  setTimeout(function(){
    reject('promise1')
  },2000)
})
			
var promise2 = new Promise(function(resolve,reject){
  setTimeout(function(){
    resolve('promise2')
  },3000)
})
 
var promise3 = Promise.resolve('promise3')
 
var promise4 = Promise.reject('promise4')
 
// 该函数接受一个 promise 数组(通常是一个可迭代对象)作为参数:
Promise.allSettled([promise1,promise2,promise3,promise4])
.then((args) => {
  console.log(args);
  // 当所有的输入 promises 都被 fulfilled 或 rejected 时,
  // statusesPromise 会解析为一个具有它们状态的数组
  /*
  result: 
  [
    {"status":"rejected","reason":"promise1"}, 
    {"status":"fulfilled","value":"promise2"},
    {"status":"fulfilled","value":"promise3"}, 
    {"status":"rejected","reason":"promise4"}
  ]*/
})

对allSellted返回结果的处理: 

//过滤出成功的请求
const successfulPromises = result.filter(p => p.status === 'fulfilled');

const errors = result
    .filter(p => p.status === 'rejected')
    .map(p => p.reason);//过滤出失败的请求,并输出原因

七.Promise深入

1.如何改变promise的状态?

1) resolve:如果当前是pendding就会变为resolved

2) reject:如果当前是pendding就会变为rejected

3) 抛出异常:如果当前是pendding就会变成rejected

const p = new Promise((resolve,reject) => {
    throw new Error('error');
)

2. 一个Promise指定了多个成功或失败的回调,是否都会调用?

     都会调用,可以在多个失败/成功的回调中执行不同的操作

const p1 = new Promise((resolve,reject) => {
    resolve(1);
});
p1.then(
    value => {console.log('1',value)},
    reason => {}
);
p1.then(
    value => {console.log('2',value)},
    reason => {}
);

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA4KaP546W5Ly0,size_20,color_FFFFFF,t_70,g_se,x_16

3.改变Promise状态和指定回调的谁先谁后?

    都有可能,正常情况下是先指定回调函数,再修改状态但也可以先改状态再指定回调。

  •  如何先改状态再指定回调?

    在执行器中直接调用resolve和reject,延迟更长时间再调用then

常规用法:先定回调,再改状态


new Promise((resolve,reject) => {
    setTimeout(() => {
        // 2.后改变状态,异步执行回调函数
        reolve(1);
    },1000)
}).then(
    // 1. 先指定回调
    value => {},
    reason => {}
)

 改变状态,再指定回调

// Promise内代码为同步
new Promise((resolve,reject) => {
    // 1.先改变状态(同步代码)
    resolve(1);
}).then(
    // 2. 指定回调
    value => {},
    reason => {}
);

// Promise内代码为异步
const p = new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve(1);
    },1000)
});
setTimeout(() => {
    p.then(
        value => {},
        reason => {}
    )
},1100);
  • 什么时候得到数据?

若先指定回调,那当状态发生改变时,回调函数就会调用,得到数据;

如果先改变状态,那当指定回调时,回调函数就会调用,得到数据;

4.promise.then返回的新Promise的状态由什么决定?

由then()指定的回调函数的执行结果决定

1)如果抛出异常,新Promise变为rejected,reason为抛出的异常

2)如果返回的是非Promise的任意值,新Promise变为resolved,value为返回的值

3)如果返回的是另一个新的Promise,此Promise的结果就是新Promise的结果

抛出异常 

new Promise((resolve,reject) => {
    resolve(1);
}).then(
    value => {
        console.log(value);   // 1
        throw 2;
    },
    reason => {
        console.log(reason)
    }
).then(
    value => {
        console.log(value)
    },
    reason => {
        console.log(reason);   // 2
    }
)

 返回非Promise(其一)

new Promise((resolve,reject) => {
    resolve(1);
}).then(
    value => {
        console.log(value);  // 1
        return 2;
    },
    reason => {
        console.log(reason)
    }
).then(
    value => {
        console.log(value);  // 2
    },
    reason => {
        console.log(reason);  
    }
)

返回非Promise(其二),不手动写返回值会默认返回undefined

new Promise((resolve,reject) => {
    reject(1);
}).then(
    value => {
        console.log(value); 
    },
    reason => {
        console.log(reason);  // 1
    }
).then(
    value => {
        console.log(value);  // undefined
    },
    reason => {
        console.log(reason);  
    }
)

返回Promise类型,此时返回的是一个成功Promise,那么then回调的执行结果就是成功的Promise

new Promise((resolve,reject) => {
    resolve(1);
}).then(
    value => {
        console.log(value);  // 1
        return Promise.resolve(2);
    },
    reason => {
        console.log(reason)
    }
).then(
    value => {
        console.log(value);  // 2
    },
    reason => {
        console.log(reason);  
    }
)

5.Promise异常穿透

当使用Promise的then链式调用时,可以在最后指定失败的回调catch;前面的任何操作出现了异常,都会传到最后失败的回调进行处理,例如:

new Promise((resolve,reject) => {
    reject(1);
}).then(
    value => {
        return 2;
    }
).then( 
    value => {
        return 3
    }
).catch(
    reason => {
        console.log(reason);  // 1
    }
)

创建了一个结果为失败的Promise实例,失败的结果会直接被最后的catch捕获,但前提是代码中的then链式调用不能写失败的回调,如上述代码所示。如果写了then方法的失败回调,该失败的结果就被这个失败的回调捕获处理,不会在catch中进行处理了。

不写失败的回调,Promise底层会默认添加一个失败的回调并抛出异常,如下:

new Promise((resolve,reject) => {
    reject(1);
}).then(
    value => {
        return 2;
    },
    // reason => {throw reason}   --->   不写失败的回调会默认加上的代码
).then( 
    value => {
        return 3
    },
    // reason => {throw reason}
).catch(
    reason => {
        console.log(reason);  // 1
    }
)

这样才能实现异常的穿透,当然如果你非要写失败的回调同时还想让catch去处理失败的结果,可以这样写:

new Promise((resolve,reject) => {
    reject(1);
}).then(
    value => {
        return 2;
    },
    reason => Promise.reject(reason)   
).then( 
    value => {
        return 3
    },
    reason => Promise.reject(reason) 
).catch(
    reason => {
        console.log(reason);  // 1
    }
)

6.中断Promise链

当Promise链式调用时,在中间中断,不再调用后面的回调函数;可以给回调函数返回一个状态为pendding的Promise对象,即 new Promise(() => {}) 

new Promise((resolve, reject) => {
    reject(1);
}).then(
    value => {
        return 2;
    },
    reason => {
        return new Promise(() => {});
    }
).then(
    value => {
        return 3
    },
    reason => new Promise(() => {})
)

7.如何保证Promise.all始终返回成功的状态

Promise.all返回成功的条件是每个Promise都是成功的返回结果,若有失败则不会返回成功的状态,所以可以使用catch捕获这些Promise实例的失败的结果,返回一个非Promise的类型的值,这样all处理的每个Promise状态都能保证是成功的。

const a = Promise.resolve(1);
const b = Promise.reject(new Error(2));
const c = Promise.resolve(3);

const arr = [a, b, c].map(p => p.catch(e => e));

Promise.all(arr)
    // [1, Error: 2 at http://127.0.0.1:5500/es6/e12.html:164:34, 3]
    .then(results => console.log(results)) 
    .catch(e => console.log(e));

八.自定义Promise

1.定义Promise构造函数

在构造函数中,需要做如下几个操作:

1)定义构造函数的属性

2)调用执行器,捕获异常

3)修改Promise的状态并执行相关操作(即调用resolve和reject)

function Promise(excutor) {
    // 属性值
    ......

    // 定义成功和失败的方法
    resolve() {......}
    reject() {......}

    // 调用执行器
    excutor(resolve,reject);
}

1)定义构造函数的属性

首先定义Promise构造函数,先要为构造函数添加属性,分别是Promise的状态PromiseState ,Promise的返回结果PromiseResult ,以及then方法中定义的回调函数callback ,上文提到过,一个Promise实例可以指定多个成功或失败的回调,所以callback属性需要写成数组形式

this.PromiseState = 'pending';
this.PromiseResult = null;
this.callback = [];

2)调用执行器,捕获异常

随后定义并调用执行器,执行器即Promise实例化时传递的参数,是一个回调函数,回调函数有两个参数,分别对应的resolve和reject;

还需考虑的一点便是执行器中抛出异常的问题,由于Promise执行器抛出异常后该Promise状态为失败,所以需要使用try...catch捕获异常,并调用方才定义的reject方法修改状态。

 try{
     //同步调用执行器函数,try catch要加在这外面,这样才能捕获throw的内容
     executor(resolve,reject);
 } catch(e){
     //修改Promise对象状态为失败
     reject(e);
 }

3)修改Promise的状态

接下来就是定义resolve和reject方法,两个方法实现的方式相类似,需要做的就是修改当前Promise实例的状态,即PromiseState 的值,以及获取结果数据PromiseResult ,最后是执行对应的回调函数callback,由于执行回调函数是异步操作,所以使用setTimeout进行包裹,将该操作放在消息对象中异步执行;


    function resolve(data){
        //判断当前的状态,确保Promise只能修改一次状态
        if (self.PromiseState !== "pending") return; 
        self.PromiseState = 'fulfilled';
        self.PromiseResult = data;
        //异步操作执行完毕后执行
        self.callback.forEach(item =>{
            //将then方法中的回调改为异步执行
            setTimeout(() => {
                item.onResolved(data);
            })
        });
    }
    function reject(data){......}
  function Promise(executor){
    //添加属性
    this.PromiseState = 'pending';
    this.PromiseResult = null;
    this.callback = [];
    const self = this;

    function resolve(data){
        //判断当前的状态,确保Promise只能修改一次状态
        if (self.PromiseState !== "pending") return; 
        self.PromiseState = 'fulfilled';
        self.PromiseResult = data;
        //异步操作执行完毕后执行
        self.callback.forEach(item =>{
            //将then方法中的回调改为异步执行
            setTimeout(() => {
                item.onResolved(data);
            })
        });
    }
    function reject(data){
        if (self.PromiseState !== "pending") return; 
        self.PromiseState = 'rejected';
        self.PromiseResult = data;
        //异步操作执行完毕后执行,改变状态后再去执行回调
        self.callback.forEach(item =>{
            setTimeout(() => {
                item.onRejected(data);              
            })
        });

    }
    try{
        //同步调用执行器函数,try catch要加在这外面,这样才能捕获throw的内容
        executor(resolve,reject);
    } catch(e){
        //修改Promise对象状态为失败
        reject(e);
    }
}

2.定义then方法

执行then方法主要需要执行以下几个操作:

1).判断当前的Promise实例的状态

2).确定then方法返回的Promise的状态

3).指定回调函数的默认值

1) 判断当前的Promise实例的状态 

上文说过,当执行了then方法,当前Promise的状态可能是pending或resolve、reject,这是由于Promise实例先改变状态还是先执行回调这两者的先后顺序决定的,如果Promise封装的是一个网络请求,由于请求是异步的,而then方法的执行是同步的,所以此时是先指定then回调,再修改状态,那么执行then方法的状态就是pending,此时需要将回调函数保存在callback中,等异步操作执行完后再执行then的成功/失败的回调,而执行成功或失败的回调是在Promise构造函数内执行的。

if (this.PromiseState === 'pending') {
    //保存回调函数
    this.callback.push({
        onResolved:function(){
            callback(onResolved);
        },
        onRejected:function(){
            callback(onRejected);
        }
    });
}

如果是先修改了状态,再执行回调,那么此时then方法的Promise实例状态为fulfilled或rejected,那么需要执行对应的成功或失败的then回调,并根据回调的返回值决定返回Promise的状态

if (this.PromiseState === 'fulfilled') {
    //将then方法中的回调改为异步执行
    setTimeout(() => {
        callback(onResolved);
    })
}
if (this.PromiseState === 'rejected') {
    setTimeout(() => {
        callback(onRejected);
    })
}

2) 确定then方法返回的Promise状态

then方法是有返回值的,且是一个Promise,返回的promise的结果由onResolved/onRejected执行结果决定:

 1.若抛出异常,返回的Promise结果为失败,reason为异常;

 2.若返回的是Promise,返回的Promise的结果就是这个结果;

 3.若返回的不是Promise,返回的Promise为成功,value就是返回值。

这里定义callback方法就是用来处理onResolved/onRejected执行结果和then方法返回结果之间的映射关系。

return new Promise((resolve,reject) =>{
    function callback(type) {
        try{
            //获取回调函数的执行结果
            let result = type(self.PromiseResult);
            //判断
            if (result instanceof Promise) {
                //当回调返回的是一个Promise实例时
                result.then(v => {
                    //result成功调用resolve方法,让返回的Promise也成功
                    resolve(v);
                },r => {
                    //result失败调用reject方法,让返回的Promise也失败
                    reject(r);
                });
                // result.then(resolve,reject);
            } else {
                //当返回的是基本数据类型时
                //默认成功调用resolve方法
                resolve(result);
            }
        }catch(e){
            reject(e);
        }
    }
}

3) 指定回调函数的默认值

指定失败回调的默认值,是为了解决异常穿透问题;此外catch的封装也是基于then方法实现,而catch方法也是可以继续通过.then继续链式调用的,所以还要指定成功回调的默认值,将成功的结果继续向下传递。

if (typeof onRejected !== 'function') {
    //如果then方法的第二个参数不传递,就默认传递以下这个回调函数
    onRejected = reason => {
        throw reason;
    }
}
if (typeof onResolved !== 'function') {
    onResolved = value => value;
}

 完整代码: 

Promise.prototype.then = function(onResolved,onRejected){
    let self = this;
    //判断回调函数的第二个参数,不是一个函数就抛出异常,解决异常穿透
    if (typeof onRejected !== 'function') {
        //如果then方法的第二个参数不传递,就默认传递以下这个回调函数
        onRejected = reason => {
            throw reason;
        }
    }
    if (typeof onResolved !== 'function') {
        onResolved = value => value;
    }
    //then方法返回的是一个promise对象
    return new Promise((resolve,reject) =>{
        function callback(type) {
            try{
                //获取回调函数的执行结果
                let result = type(self.PromiseResult);
                //判断
                if (result instanceof Promise) {
                    //当回调返回的是一个Promise实例时
                    result.then(v => {
                        //result成功调用resolve方法,让返回的Promise也成功
                        resolve(v);
                    },r => {
                        //result失败调用reject方法,让返回的Promise也失败
                        reject(r);
                    });
                    // result.then(resolve,reject);
                } else {
                    //当返回的是基本数据类型时
                    //默认成功调用resolve方法
                    resolve(result);
                }
            }catch(e){
                reject(e);
            }
        }
        //调用成功的回调函数
        if (this.PromiseState === 'fulfilled') {
            //将then方法中的回调改为异步执行
            setTimeout(() => {
                callback(onResolved);
            })
        }
        if (this.PromiseState === 'rejected') {
            setTimeout(() => {
                callback(onRejected);
            })
        }
        //执行的是异步任务,当状态依旧是是pending时,先要保存onResolved,onRejected
        //到callback属性中,当异步操作执行完毕后再执行这两个回调函数
        if (this.PromiseState === 'pending') {
            // debugger;
            //保存回调函数
            this.callback.push({
                onResolved:function(){
                    callback(onResolved);
                },
                onRejected:function(){
                    callback(onRejected);
                }
            });
        }
    })

}

3.定义catch方法

catch方法实际上就是基于then的二次封装,需要注意的是catch之后依旧可以继续使用then方法链式调用的,所以需要将封装的then方法作为返回值返回。

Promise.prototype.catch = function(onRejected) {
    return this.then(undefined,onRejected); 
}

4.定义resolve方法

返回的是一个成功/失败的Promise

如果参数是一般值,Promise成功,value就是这个值;

如果参数是成功的Promise,Promise成功,value是这个Promise的value

如果参数是失败的的Promise,Promise失败,reason是这个Promise的reason

//添加resolve方法,该方法属于Promise函数对象的,不是实例对象,无需在原型上添加
Promise.resolve = function(value) {
    return new Promise((resolve,reject) => {
        if (value instanceof Promise) {
            value.then(s => {
                resolve(s);
            }, f => {
                reject(f);
            });
        } else {
            resolve(value);
        }
    });
};

5.定义reject方法

返回的只能是一个失败的Promise

Promise.reject = function(value) {
    return new Promise((resolve,reject) => {
        reject(value);
    })
};

6.定义all方法

Promise.all = function(promises) {
    return new Promise((resolve,reject) => {
        let arr = []; // 定义一个数组,用来存放成功时返回的各个Promise的返回结果
        let count = 0; // 定义一个变量用于计数
        for (let i = 0; i < promises.length; i++) {
            Promise.resolve(promises[i]).then(s => {
                arr[i] = s;
                count++;
                //判断条件为真代表每一个Promise都是成功的结果,此时可以调用resolve方法
                if (count === promises.length) {
                    resolve(arr);
                }
            },f => {
                //只要存在失败的Promise就直接调用reject方法
                reject(f); 
            })            
        }
    })
};

7.定义race方法

Promise.race = function(promises){
    return new Promise((resolve,reject) => {
        for (let i = 0; i < promises.length; i++) {
            Promise.resolve(promises[i]).then(s => {
                resolve(s);
            },f => {
                reject(f);
            })            
        }
    })
}

8.定义allSettled方法

Promise.allSettled = function (promises) {
    return new Promise(resolve => {
      const data = [], len = promises.length;
      let count = len;
      for (let i = 0; i < len; i += 1) {
        const promise = promises[i];
        promise.then(res => {
          data[i] = { status: 'fulfilled', value: res };
        }, error => {
          data[i] = { status: 'rejected', reason: error };
        }).finally(() => { 
          if (!--count) {
            resolve(data);
          }
        });
      }
    });
  }

九.js宏队列和微队列

何为同步和异步?

同步:任务是依次执行的,上一个任务没有完成,下一个任务不可能进行处理;

异步:上一个事情没有完成,下一个事情也可以处理。


在Promise的实际使用中,绝大多数情况封装的是异步代码,它们是在同步代码之后执行的,这是因为js是单线程的机制导致的,下面说明一下js代码执行先后顺序的原理:

js中用来存储执行回调函数的队列包含两个不同的特定队列,分别是宏队列微队列

宏队列用来保存待执行的宏任务,包括定时器回调,DOM事件回调,ajax回调

微任务用来保存待执行的微任务的队列,包括promise的回调,MutationObserver的回调;


js代码执行顺序:在自上而下执行同步代码时发现异步代码会将其放入等待任务队列,随后主线程会继续向下执行,js引擎会首先执行所有的初始化同步代码,此时主线程处于空闲状态,此时主线程回去等待事件队列Event Queue中挨个取出到达时间点的异步任务,每次准备取出第一个宏任务执行前,都需要将所有的微任务一个一个取出来执行。(先执行完微任务,再执行宏任务),一个执行完毕继续取出下一个需要执行的异步任务,这整个反反复复的过程称为事件循环

注意:setTimeout中,延迟时间为0也不会立刻执行,浏览器默认会存在一个最小等待时间,大概是10ms。

猜你喜欢

转载自blog.csdn.net/weixin_43655896/article/details/122802407