学习vue写项目已经很久了,在项目中有许多地方会使用到Promise,无论前后端的接口通信,还是各种第三方的组件库或是插件,或多或少都会发现Promise的影子,所以今天简单梳理一下关于Promise的知识体系。
目录
2. 一个Promise指定了多个成功或失败的回调,是否都会调用?
4.promise.then返回的新Promise的状态由什么决定?
一.什么是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导致的
1.3. TypeError:类型错误
在浏览器控制台输入如下代码
let b = null;
console.log(b.xxx);
需要在b上读取xxx属性的前提是b必须是一个对象,但b是null非对象,报类型错误
1.4. RangeError:数值不在其所允许的范围内
function fn() {
fn(); // 函数递归调用
}
fn();
在fn函数内递归调用自己,但需要一个调用次数的限制,由没有定义限制导致超过了限制,所以报超过范围错误
1.5. SyntaxError:语法错误
const a = """";
双引号内不能包裹双引号,这是语法错误
当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);
})
控制台输出:
五.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 => {}
);
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。