挑战坚持学习1024天——前端之JavaScript高级
js基础部分可到我文章专栏去看 ---点击这里
Day57【2022年9月19日】
学习重点:异步编程之初识primose(期约)
1.1primose背景
为弥合现有实现之间的差异,2012 年 Promises/A+组织分叉(fork)了 CommonJS 的 Promises/A 建议,并以相同的名字制定了 Promises/A+规范。这个规范最终成为了ECMAScript 6 规范实现的范本。ECMAScript 6 增加了对 Promises/A+规范的完善支持,即 Promise 类型。一经推出,Promise 就大受欢迎,成为了主导性的异步编程机制。所有现代浏览器都支持 ES6 期约,很多其他浏览器 API(如fetch()和 Battery Status API)也以期约为基础。
1.2promise简单运用
Promise是一个类,可以翻译成 承诺、许诺 、期约;
当我们需要的时候,给予调用者一个承诺:待会儿我会给你回调数据时,就可以创建一个Promise的对象;
在通过new创建Promise对象时,我们需要传入一个回调函数,我们称之为executor(执行器)
- 这个回调函数会被立即执行,并且给传入另外两个回调函数resolve、reject;
- 当我们调用resolve回调函数时,会执行Promise对象的then方法传入的回调函数;
- 当我们调用reject回调函数时,会执行Promise对象的catch方法传入的回调函数;
可以通过 new 操作符来实例化。创建新期约时需要传入执行器(executor)函数作为参数 在 Promise 实例创建后,执行器里的逻辑会立刻执行,在执行的过程中,根据异步返回的结果,决定如何使用 resolve 或 reject 来改变 Promise实例的状态。 Promise 实例有三种状态:
• pending 状态,表示进行中。这是 Promise 实例创建后的一个初始态; • fulfilled 状态,表示成功完成。这是我们在执行器中调用 resolve 后,达成的状态; • rejected 状态,表示操作失败、被拒绝。这是我们在执行器中调用 reject后,达成的状态。 Promise实例的状态是可以改变的,但它只允许被改变一次。当我们的实例状态从 pending 切换为 rejected 后,就无法再扭转为 fulfilled,反之同理。当 Promise 的状态为 resolved 时,会触发其对应的 then 方法入参里的 onfulfilled 函数;当 Promise 的状态为 rejected 时,会触发其对应的 then 方法入参里的 onrejected 函数。(promise第一次状态被改变后将无法改变)
实例
console.log("Start");
//promise初始状态为pending 改变为其他状态后将不会再改变
let p = new Promise(function(resolve, reject){
//这里会同步执行
console.log("Promise");
//resolve/reject是异步代码
//调用resolve()时,后续的then()方法被调用
resolve('success');
//调用reject()时,后续的catch()方法被调用
console.log("End");
reject('error'); //不会被执行
});
p.then(function(msg){
console.log(msg);
}).catch(function(err){
console.log(err);
});
//reject()方法的参数会原封不动地作为catch()方法的参数
let p1 = new Promise(function(resolve, reject){
reject('error');
}
);
p1.catch(function(err){
console.log(err);
});
// Start
// Promise
// End
// success
// error
promise resolve遇上console.log,reject也是如此
//下面两个代码是等价的
let a = new Promise((resolve, reject) => {
resolve(console.log(1)); //相当先执行console.log(1) 后执行resolve()
});
console.log(2);
console.log(a);
let a1 = new Promise((resolve, reject) => {
console.log(1);
resolve();
});
console.log(2);
console.log(a1);
解决了回调地狱
2.今日精进
做事按照既定流程,迈出第一步时已经成功一半了。
Day58-Day59【2022年9月20日-21日】
学习重点:异步编程之primose详解
1.1 执行器(Executor)
Executor是在创建Promise时需要传入的一个回调函数,这个回调函数会被立即执行,并且传入两个参数(resolve, reject) 通常我们会在Executor中确定我们的Promise状态:
通过resolve,可以兑现(fulfilled)Promise的状态,我们也可以称之为已决议(resolved);
通过reject,可以拒绝(reject)Promise的状态;
实例:
new Promise((resolve, reject) => {
console.log("executor");
})
1.2 Promise 方法
1.2.1 resolve
1.2.1.1 基本用法
期约并非一开始就必须处于待定状态,然后通过执行器函数才能转换为落定状态。通过调用 Promise.resolve()静态方法,可以实例化一个解决的期约。下面两个期约实例实际上是一样的:
let p1 = new Promise((resolve, reject) => resolve());
let p2 = Promise.resolve();
语法
Promise.resolve(value);
接收一个参数value表示将被 Promise 对象解析的参数,也可以是一个Promise 对象,或者是一个 thenable。(三种)
then()作为promise的配套组件,我们只需要验证有没有then()就可以了,所以,我们提出了thenable的概念。thenable:任何含有then()方法的对象或函数。它的结构是不是很像promise,它们都有一个then()。所以,我们无法找出promise时,就退而求其次,先找类似promise的,也就是thenable。 thenable文章[详解](blog.csdn.net/weixin_4230…)
返回值
返回一个带着给定值解析过的 Promise 对象,如果参数本身就是一个 Promise 对象,则直接返回这个 Promise 对象。
详情可看这
实例
//resolve
let p2 = new Promise(function(resolve, reject){
resolve('success');
});
p2.then(function(msg){
console.log(msg);
});
let p3 = Promise.resolve('success');
p3.then(function(msg){
console.log(msg);
});
Promise.resolve('success').then(function(msg){
console.log(msg);
});
//success
//success
//success
以上三种写法等价
对这个静态方法而言,如果传入的参数本身是一个期约,那它的行为就类似于一个空包装。因此, Promise.resolve()可以说是一个幂等方法,如下所示:
let p = Promise.resolve(7);
setTimeout(console.log, 0, p === Promise.resolve(p));
// true
setTimeout(console.log, 0, p === Promise.resolve(Promise.resolve(p)));
// true
这个幂等性会保留传入期约的状态:
let p = new Promise(() => {});
setTimeout(console.log, 0, p); // Promise <pending>
setTimeout(console.log, 0, Promise.resolve(p)); // Promise <pending>
setTimeout(console.log, 0, p === Promise.resolve(p)); // true
1.2.1.2resolve不同值的区别
- 情况一:如果resolve传入一个普通的值或者对象,那么这个值会作为then回调的参数;
//resovle 传入普通值或对象
new Promise((resolve, reject) => {
resolve('normal resolve');
}).then((msg) => {
console.log(msg);
});
//normal resolve
- 情况二:如果resolve中传入的是另外一个Promise,那么这个新Promise会决定原Promise的状态:
//resolve 传入promise对象
new Promise((resolve, reject) => {
resolve(new Promise((resolve, reject) => {
setTimeout(()=>{
resolve('promise resolve');
},1000);
}));
}).then((msg) => {
console.log(msg);
}).catch((err) => {
console.log(err);
});
- 情况三:如果resolve中传入的是一个对象,并且这个对象有实现then方法,那么会执行该then方法,并且根据then方法的结果来决定Promise的状态:
//resolve 传入有then方法的对象
//状态由then方法决定
new Promise((resolve, reject) => {
resolve({
then: function(resolve, reject){
// resolve('object resolve');
reject('object reject');
}
});
}).then((msg) => {
console.log(msg);
}).catch((err)=>{
console.log(err);
})
1.2.2 then
1.2.2.1基本用法
then方法是Promise对象上的一个方法(实例方法)它其实是放在Promise的原型上的 Promise.prototype.then()是为期约实例添加处理程序的主要方法。这个 then()方法接收最多两个参数:onResolved 处理程序和 onRejected 处理程序。这两个参数都是可选的,如果提供的话,则会在期约分别进入“兑现”和“拒绝”状态时执行。一般情况下使用只用成功的状态。对应resolve。
语法
p.then(onFulfilled[, onRejected]);
p.then(value => {
// fulfillment
}, reason => {
// rejection
});
then方法接受两个参数:
fulfilled的回调函数:当状态变成fulfilled时会回调的函数;
reject的回调函数:当状态变成reject时会回调的函数;
两个参数都是可选
返回值
then方法返回的是一个新的Promise实例(不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
由下图可看到promise的返回值
当一个
Promise完成(fulfilled)或者失败(rejected)时,返回函数将被异步调用(由当前的线程循环来调度完成)。具体的返回值依据以下规则返回。如果then中的回调函数:
- 返回了一个值,那么
then返回的 Promise 将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。- 没有返回任何值,那么
then返回的 Promise 将会成为接受状态,并且该接受状态的回调函数的参数值为undefined。- 抛出一个错误,那么
then返回的 Promise 将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。- 返回一个已经是接受状态的 Promise,那么
then返回的 Promise 也会成为接受状态,并且将那个 Promise 的接受状态的回调函数的参数值作为该被返回的 Promise 的接受状态回调函数的参数值。- 返回一个已经是拒绝状态的 Promise,那么
then返回的 Promise 也会成为拒绝状态,并且将那个 Promise 的拒绝状态的回调函数的参数值作为该被返回的 Promise 的拒绝状态回调函数的参数值。- 返回一个未定状态(
pending)的 Promise,那么then返回 Promise 的状态也是未定的,并且它的终态与那个 Promise 的终态相同;同时,它变为终态时调用的回调函数参数与那个 Promise 变为终态时的回调函数的参数是相同的。
总结如下
当then方法中的回调函数本身在执行的时候,那么它处于pending状态;
当then方法中的回调函数返回一个结果时,那么它处于fulfilled状态,并且会将结果作为resolve的参数;
情况一:返回一个普通的值;
情况二:返回一个Promise;
情况三:返回一个thenable值;
当then方法抛出一个异常时,那么它处于reject状态;
实例
//then
//onFulfilled/onRejected是可选参数
//如果不是函数,会被忽略
//如果onFulfilled/onRejected返回一个值,会被传递给下一个then的onFulfilled
//如果onFulfilled/onRejected抛出一个异常,会被传递给下一个then的onRejected
//如果onFulfilled/onRejected返回一个promise对象,会等待这个promise对象的状态改变,再传递给下一个then的onFulfilled/onRejected
let promise = new Promise((resolve, reject) => {
resolve('success');
// reject('error');
});
promise.then((res)=>{
console.log(res);
}, (err)=>{
console.log(err);
});
//等价于
promise.then((res)=>{
console.log(res);
}).catch((err)=>{
console.log(err);
});
1.2.2.2 链式调用
一个Promise的then方法是可以被多次调用的:每次调用我们都可以传入对应的fulfilled回调;当Promise的状态变成fulfilled的时候,这些回调函数都会被执行;
实例
//then链式调用
// 用法1
let promise = new Promise((resolve, reject) => {
resolve('success');
});
promise.then((res)=>{
console.log(res);
return 'success1';
}).then((res)=>{
console.log(res);
return 'success2';
}).then((res)=>{
console.log(res);
});
//success
//success1
//success2
//用法2
let promise1 = new Promise((resolve, reject) => {
resolve('success');
});
promise1.then((res)=>{
console.log(res);
}).then((res)=>{
console.log(res);
}).then((res)=>{
console.log(res);
}
);
//success
//undefined
//undefined
//用法3
let promise2 = new Promise((resolve, reject) => {
resolve('success');
});
promise2.then((res)=>{
console.log(res);
return new Promise((resolve, reject) => {
resolve('success1');
});
}).then((res)=>{
console.log(res);
return new Promise((resolve, reject) => {
resolve('success2');
});
}).then((res)=>{
console.log(res);
});
//success
//success1
//success2
//用法4
let promise3 = new Promise((resolve, reject) => {
resolve('success');
});
promise3.then((res)=>{
console.log(res);
return new Promise((resolve, reject) => {
reject('error');
});
}).then((res)=>{
console.log(res);
return new Promise((resolve, reject) => {
resolve('success2');
});
}).then((res)=>{
console.log(res);
}).catch((err)=>{
console.log(err);
});
//success
//error
//用法5
let promise4 = new Promise((resolve, reject) => {
resolve('success');
});
promise4.then((res)=>{
console.log("res1:",res)
});
promise4.then((res)=>{
console.log("res2:",res)
});
// res1: success
// res2: success
Promise.resolve("foo")
// 1. 接收 "foo" 并与 "bar" 拼接,并将其结果做为下一个 resolve 返回。
.then(function(string) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
string += 'bar';
resolve(string);
}, 1);
});
})
// 2. 接收 "foobar", 放入一个异步函数中处理该字符串
// 并将其打印到控制台中,但是不将处理后的字符串返回到下一个。
.then(function(string) {
setTimeout(function() {
string += 'baz';
console.log(string);
}, 1)
return string;
})
// 3. 打印本节中代码将如何运行的帮助消息,
// 字符串实际上是由上一个回调函数之前的那块异步代码处理的。
.then(function(string) {
console.log("Last Then: oops... didn't bother to instantiate and return " +
"a promise in the prior then so the sequence may be a bit " +
"surprising");
// 注意 `string` 这时不会存在 'baz'。
// 因为这是发生在我们通过 setTimeout 模拟的异步函数中。
console.log(string);
});
由上可见,then值接收函数,如果已经被一个then处理了后面的then则接收不到resolve的值,如果想要接收到可通过resolve/return的方式去接收。
1.2.3 reject
reject方法类似于resolve方法,只是会将Promise对象的状态设置为reject状态。
Promise.reject的用法相当于new Promise,只是会调用reject
Promise.reject传入的参数无论是什么形态,都会直接作为reject
语法
Promise.reject(reason);
reason接收的拒绝的原因
返回值
一个给定原因了的被拒绝的 Promise。
实例
以下两种写法等价
let p1 = new Promise((resolve, reject) => reject());
let p2 = Promise.reject();
以下三种写法等价
let p1 = new Promise((resolve, reject) => {
reject("why");
});
p1.catch((err) => {
console.log(err);
});
let p2 = Promise.reject("why");
p2.catch((err) => {
console.log(err);
});
Promise.reject("why").catch((err) => {
console.log(err);
});
关键在于,Promise.reject()并没有照搬 Promise.resolve()的幂等逻辑。如果给它传一个期约对象,则这个期约会成为它返回的拒绝期约的理由:
setTimeout(console.log, 0, Promise.reject(Promise.resolve()))
// Promise <rejected>: Promise <resolved>
1.2.3.1 resolve和reject幂等性验证
resolve是幂等性 reject非幂等
//返回值是一个promise对象 类似一个空包装 幂等
let p1 = Promise.resolve("success");
console.log(p1);//Promise {<fulfilled>: 'success'}
console.log(Promise.resolve(p1)); //Promise {<fulfilled>: 'success'}
console.log(Promise.resolve(p1) === p1); //true
//返回值是一个带理由的promise对象 非幂等
let p2 = Promise.reject("error");
console.log(p2);//Promise {<rejected>: 'error'}
console.log(Promise.reject(p2)); //PPromise {<rejected>: Promise}
console.log(Promise.reject(p2) === p2); //false
1.2.4 catch
Promise.prototype.catch()方法用于给期约添加拒绝处理程序。这个方法只接收一个参数: onRejected 处理程序。事实上,这个方法就是一个语法糖,调用它就相当于调用 Promise.prototype.then(null, onRejected)。
语法
p.catch(onRejected);
p.catch(function(reason) {
// 拒绝
});
onRejected 当 Promise 被 rejected 时,被调用的一个Function。 该函数拥有一个参数:reason rejection 的原因。如果 onRejected 抛出一个错误或返回一个本身失败的 Promise , 通过 catch() 返回的 Promise 被 rejected;否则,它将显示为成功(resolved)。 返回值
一个新的promise实例与原来不相等
let p1 = new Promise(() => {});
let p2 = p1.catch();
setTimeout(console.log, 0, p1); // Promise <pending>
setTimeout(console.log, 0, p2); // Promise <pending>
setTimeout(console.log, 0, p1 === p2); // false
实例
Promise.prototype.catch(onRejected) 等价于Promise.prototype.then(undefined, onRejected)
let p1 = Promise.reject("error");
//以下两种写法等价
p1.catch((err) => {
console.log(err);
});
p1.then(undefined,(err)=>{
console.log(err);
})
//也可以用这种写法
// let p = Promise.reject();
// let onRejected = function(e) {
// setTimeout(console.log, 0, 'rejected');
// };
// // 这两种添加拒绝处理程序的方式是一样的:
// p.then(null, onRejected); // rejected
// p.catch(onRejected); // rejected
1.2.5 all
Promise.all() 方法接收一个 promise 的 iterable 类型(注:Array,Map,Set 都属于 ES6 的 iterable 类型)的输入,并且只返回一个Promise实例, 那个输入的所有 promise 的 resolve 回调的结果是一个数组。这个Promise的 resolve 回调执行是在所有输入的 promise 的 resolve 回调都结束,或者输入的 iterable 里没有 promise 了的时候。它的 reject 回调执行是,只要任何一个输入的 promise 的 reject 回调执行或者输入不合法的 promise 就会立即抛出错误,并且 reject 的是第一个抛出的错误信息。
它的作用是将多个Promise包裹在一起形成一个新的Promise;新的Promise状态由包裹的所有Promise共同决定:当所有的Promise状态变成resolved状态时,新的Promise状态为resolved,并且会将所有Promise的返回值组成一个数组;当有一个Promise状态为reject时,新的Promise状态为reject,并且会将第一个reject的返回值作为参数。
语法
Promise.all(iterable);
iterable一个可迭代对象,如 Array 或 String。
返回值
- 如果传入的参数是一个空的可迭代对象,则返回一个已完成(already resolved)状态的 Promise。
- 如果传入的参数不包含任何 promise,则返回一个异步完成(asynchronously resolved)Promise。注意:Google Chrome 58 在这种情况下返回一个已完成(already resolved)状态的 Promise。
- 其它情况下返回一个处理中(pending)的Promise。这个返回的 promise 之后会在所有的 promise 都完成或有一个 promise 失败时异步地变为完成或失败。 见下方关于“Promise.all 的异步或同步”示例。返回值将会按照参数内的 promise 顺序排列,而不是由调用 promise 的完成顺序决定。
完成(Fulfillment): 如果传入的可迭代对象为空,Promise.all 会同步地返回一个已完成(resolved)状态的promise。 如果所有传入的 promise 都变为完成状态,或者传入的可迭代对象内没有 promise,Promise.all 返回的 promise 异步地变为完成。 在任何情况下,Promise.all 返回的 promise 的完成状态的结果都是一个数组,它包含所有的传入迭代参数对象的值(也包括非 promise 值)。
失败/拒绝(Rejection): 如果传入的 promise 中有一个失败(rejected),Promise.all 异步地将失败的那个结果给失败状态的回调函数,而不管其它 promise 是否完成。
实例 调用all方法时的结果成功的时候是回调函数的参数也是一个数组,这个数组按顺序保存着每一个promise对象resolve执行时的值。(接收后)
let p1 = new Promise((resolve, reject) => {
resolve("success1");
});
let p2 = new Promise((resolve, reject) => {
resolve("success2");
});
let p3 = new Promise((resolve, reject) => {
resolve("success3");
});
Promise.all([p1, p2, p3]).then((res) => {
console.log(res);
}).catch((err) => {
console.log(err);
});
//[ 'success1', 'success2', 'success3' ]
如果至少有一个包含的期约待定,则合成的期约也会待定。如果有一个包含的期约拒绝,则合成的 期约也会拒绝:
// 永远待定
let p1 = Promise.all([new Promise(() => {})]);
setTimeout(console.log, 0, p1); // Promise <pending>
// 一次拒绝会导致最终期约拒绝
let p2 = Promise.all([
Promise.resolve(),
Promise.reject(),
Promise.resolve()
]);
setTimeout(console.log, 0, p2); // Promise <rejected>
// Uncaught (in promise) undefined
1.2.5.1 allSettled
all方法有一个缺陷: 当有其中一个Promise变成reject状态时,新Promise就会立即变成对应的reject状态。那么对于resolved的,以及依然处于pending状态的Promise,我们是获取不到对应的结果的;在ES11(ES2020)中,添加了新的API Promise.allSettled:
// new Promise(() => {}) 表达待定状态的promise对象
//resolve状态
let p1 = new Promise((resolve, reject) => {
resolve("success1");
});
let p2 = new Promise((resolve, reject) => {
resolve("success2");
});
let p3 = new Promise((resolve, reject) => {
resolve("success3");
});
//padding状态
let p4 = new Promise(() => {});
//reject状态
let p5 = new Promise((resolve, reject) => {
reject("error");
});
Promise.allSettled([p1, p2, p3,p5]).then((res) => {
console.log(res);
}).catch((err) => {
console.log(err);
});
// (4) [{…}, {…}, {…}, {…}]
// 0
// :
// {status: 'fulfilled', value: 'success1'}
// 1
// :
// {status: 'fulfilled', value: 'success2'}
// 2
// :
// {status: 'fulfilled', value: 'success3'}
// 3
// :
// {status: 'rejected', reason: 'error'}
// length
// :
// 4
// [[Prototype]]
// :
// Array(0)
该方法会在所有的Promise都有结果(settled),无论是fulfilled,还是rejected时,才会有最终的状态;并且这个Promise的结果一定是fulfilled的;但是如果存在padding状态整个promise还是返回待定状态
//resolve状态
let p1 = new Promise((resolve, reject) => {
resolve("success1");
});
let p2 = new Promise((resolve, reject) => {
resolve("success2");
});
let p3 = new Promise((resolve, reject) => {
resolve("success3");
});
//padding状态
let p4 = new Promise(() => {});
//reject状态
let p5 = new Promise((resolve, reject) => {
reject("error");
});
Promise.allSettled([p1, p2, p3,p4,p5]).then((res) => {
console.log(res);
}).catch((err) => {
console.log(err);
});
Promise {<pending>}
我们来看一下打印的结果: allSettled的结果是一个数组,数组中存放着每一个Promise的结果,并且是对应一个对象的;这个对象中包含status状态,以及对应的value值; 当您有多个彼此不依赖的异步任务成功完成时,或者您总是想知道每个promise的结果时,通常使用它。相比之下,Promise.all() 更适合彼此相互依赖或者在其中任何一个reject时立即结束。
1.2.6 race
如果有一个Promise有了结果,我们就希望决定最终新Promise的状态,那么可以使用race方法: race是竞技、竞赛的意思,表示多个Promise相互竞争,谁先有结果,那么就使用谁的结果; race方法和all一样,接受的参数是一个每项都是promise的数组,但是与all不同的是,当最先执行完的事件执行完之后,就直接返回该promise对象的值。如果第一个promise对象状态变成resolved,那自身的状态变成了resolved;反之第一个promise变成rejected,那自身状态就会变成rejected。
语法
Promise.race(iterable);
iterable可迭代对象,类似Array。详见 iterable。
返回值
一个待定的 Promise 只要给定的迭代中的一个 promise 解决或拒绝,就采用第一个 promise 的值作为它的值,从而异步地解析或拒绝(一旦堆栈为空)。
实例
let p1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
reject(1);
},2000)
});
let p2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(2);
//即使是返回失败的状态也会执行输出2
//reject(2);
},1000)
});
let p3 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(3);
},3000)
});
Promise.race([p1,p2,p3]).then((res)=>{
console.log(res)
}).catch((res)=>{
console.log(res)
});
//2
all、race、reject 和 resolve。为重点方法
1.2.7 any
该方法还属于实验性方法,并不适用于所有的浏览器 any方法是ES12中新增的方法,和race方法是类似的: any方法会等到一个fulfilled状态,才会决定新Promise的状态; 如果所有的Promise都是reject的,那么也会等到所有的Promise都变成rejected状态; 如果所有的Promise都是reject的,那么会报一个AggregateError的错误。
语法
Promise.any(iterable);
iterable 一个可迭代的对象,例如 Array。
返回值
如果传入了一个空的可迭代对象,那么就会返回一个已经被拒的 promise 如果传入了一个不含有 promise 的可迭代对象,那么就会返回一个异步兑现的 promise 其余情况下都会返回一个处于等待状态的 promise。如果可迭代对象中的任意一个 promise 兑现了,那么这个处于等待状态的 promise 就会异步地(调用栈为空时)切换至兑现状态。如果可迭代对象中的所有 promise 都被拒绝了,那么这个处于等待状态的 promise 就会异步地切换至被拒状态。
1.2.8 finally
finally是在ES9(ES2018)中新增的一个特性:表示无论Promise对象无论变成fulfilled还是rejected状态,最终都会被执行的代码。finally方法是不接收参数的,因为无论前面是fulfilled状态,还是rejected状态,它都会执行。
语法
p.finally(onFinally);
p.finally(function() {
// 返回状态为 (resolved 或 rejected)
});
onFinally Promise 结束后调用的 Function。
返回值 返回一个设置了 finally 回调函数的 Promise 对象。
实例
let p1 = new Promise((resolve,reject)=>{
// resolve("success");
reject("false");
}).then((res)=>{
console.log(res);
}).catch((err)=>{
console.log(err);
}).finally(()=>{
console.log("finally");
});
无论结果如何都会执行finally方法
如果你想在 promise 执行完毕后无论其结果怎样都做一些处理或清理时,finally() 方法可能是有用的。
finally() 虽然与 .then(onFinally, onFinally) 类似,它们不同的是:
调用内联函数时,不需要多次声明该函数或为该函数创建一个变量保存它。 由于无法知道 promise 的最终状态,所以 finally 的回调函数中不接收任何参数,它仅用于无论最终结果如何都要执行的情况。 与Promise.resolve(2).then(() => {}, () => {})(resolved 的结果为undefined)不同,Promise.resolve(2).finally(() => {}) resolved 的结果为 2。 同样,Promise.reject(3).then(() => {}, () => {}) (fulfilled 的结果为undefined), Promise.reject(3).finally(() => {}) rejected 的结果为 3。
Promise.resolve(2).then(() => {}, () => {});
//Promise {<fulfilled>: undefined} 返回值(浏览器)
Promise.resolve(2).finally(() => {});
//Promise {<fulfilled>: 2}
实例方法(挂载在原型链上) :Promise.prototype.then()、Promise.prototype.catch()、Promise.prototype.finally()、 期约实例的方法是连接外部同步代码与内部异步代码之间的桥梁。这些方法可以访问异步操作返回的数据,处理期约成功和失败的结果,连续对期约求值,或者添加只有期约进入终止状态时才会执行的代码。
2.今日精进
要控制自身情绪,保持乐观,宁愿错误的乐观,也不行正确的悲观。
Day60-61【2022年9月22日-23日】
学习重点:迭代器(Iterator)
1.什么是迭代器
1.1迭代
理解一下迭代 循环是最简单的迭代 实例
for (let i = 1; i <= 10; ++i) {
console.log(i);
}
循环是迭代机制的基础,这是因为它可以指定迭代的次数,以及每次迭代要执行什么操作。每次循环都会在下一次迭代开始之前完成,而每次迭代的顺序都是事先定义好的。迭代会在一个有序集合上进行。(“有序”可以理解为集合中所有项都可以按照既定的顺序被遍历到,特别是开始和结束项有明确的定义。)数组是 JavaScript 中有序集合的最典型例子。
let collection = ['foo', 'bar', 'baz'];
for (let index = 0; index < collection.length; ++index) {
console.log(collection[index]);
}
但是这种方式也有一定的弊端,迭代在执行操作时需要事情知道如何迭代。
迭代弊端
迭代之前需要事先知道如何使用数据结构。 数组中的每一项都只能先通过引用取得数组对象, 然后再通过[]操作符取得特定索引位置上的项。这种情况并不适用于所有数据结构。 遍历顺序并不是数据结构固有的。 通过递增索引来访问数据是特定于数组类型的方式,并不适用于其他具有隐式顺序的数据结构。
虽然后续推出了forEach也有一定弊端(只能遍历数组,无法终止循环而且回调结构也比较笨拙)。
迭代器推出的原因
ECMAScript 较早的版本中,执行迭代必须使用循环或其他辅助结构。随着代码量增加,代码会变得越发混乱。很多语言都通过原生语言结构解决了这个问题,开发者无须事先知道如何迭代就能实现迭代操作。这个解决方案就是迭代器模式。Python、Java、C++,还有其他很多语言都对这个模式提供了完备的支持。JavaScript 在 ECMAScript 6 以后也支持了迭代器模式。
1.2迭代器(Iterator)
在 JavaScript 中,迭代器是一个对象,它定义一个序列,并在终止时可能返回一个返回值。 更具体地说,迭代器是通过使用 next() 方法实现 Iterator protocol(迭代协议) 的任何一个对象, 该方法返回具有两个属性的对象: value,这是序列中的 next 值;和 done ,如果已经迭代到序列中的最后一个值,则它为 true 。如果 value 和 done 一起存在,则它是迭代器的返回值。 迭代协议请看-->这
迭代器(iterator),使用户在容器对象(container,例如链表或数组)上遍访的对象,使用该接口无需关心对象的内部实现细节。 其行为像数据库中的光标,迭代器最早出现在1974年设计的CLU编程语言中;在各种编程语言的实现中,迭代器的实现方式各不相同,但是基本都有迭代器,比如Java、Python等;
迭代器是帮助我们对某个数据结构进行遍历的对象。
在JavaScript中,迭代器也是一个具体的对象,这个对象需要符合迭代器协议(iterator protocol):
迭代器协议定义了产生一系列值(无论是有限还是无限个)的标准方式;
在JavaScript中这个标准就是一个特定的next方法;
next方法有如下的要求:
一个无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象:
done(boolean)
-
如果迭代器可以产生序列中的下一个值,则为 false。(这等价于没有指定 done 这个属性。)
-
如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 是可选的,如果它依然存在,即为迭代结束之后的默认返回值。
value
- 迭代器返回的任何 JavaScript 值。done 为 true 时可省略。
实现迭代器
const friends = ['Michael', 'Stacy', 'Andy'];
function createIterator(items) {
let i = 0;
return {
next: function() {
let done = (i >= items.length);
let value = !done ? items[i++] : undefined;
return {
done: done,
value: value
};
}
};
}
const iterator = createIterator(friends);
console.log(iterator.next()); // { done: false, value: 'Michael' }
console.log(iterator.next()); // { done: false, value: 'Stacy' }
console.log(iterator.next()); // { done: false, value: 'Andy' }
console.log(iterator.next()); // { done: true, value: undefined }
1.3迭代器协议
迭代器是一种一次性使用的对象,用于迭代与其关联的可迭代对象。迭代器 API 使用 next()方法 在可迭代对象中遍历数据。每次成功调用 next(),都会返回一个 IteratorResult 对象,其中包含迭 代器返回的下一个值。若不调用 next(),则无法知道迭代器的当前位置。 next()方法返回的迭代器对象 IteratorResult 包含两个属性:done 和 value。done 是一个布 尔值,表示是否还可以再次调用 next()取得下一个值;value 包含可迭代对象的下一个值(done 为 false),或者 undefined(done 为 true)。done: true 状态称为“耗尽”。
有以下几条规则:
- 不同迭代器直接没有联系
- 如果可迭代对象在迭代期间被修改了,那么迭代器也会反映相应的变化
- 迭代器维护着一个指向可迭代对象的引用,因此迭代器会阻止垃圾回收程序回收可迭代对象。
// 可迭代对象
let arr = ['foo', 'bar'];
// 迭代器工厂函数
console.log(arr[Symbol.iterator]); // f values() { [native code] }
// 迭代器
let iter = arr[Symbol.iterator]();
console.log(iter); // ArrayIterator {}
// 执行迭代
console.log(iter.next()); // { done: false, value: 'foo' }
console.log(iter.next()); // { done: false, value: 'bar' }
console.log(iter.next()); // { done: true, value: undefined }
2.可迭代对象及可迭代原生对象
2.1可迭代对象(iterable)
什么是可迭代对象呢
它和迭代器是不同的概念;当一个对象实现了iterable protocol协议时,它就是一个可迭代对象; 这个对象的要求是必须实现 @@iterator 方法,在代码中我们使用 Symbol.iterator 访问该属性;若一个对象拥有迭代行为,比如在 for...of 中会循环哪些值,那么那个对象便是一个可迭代对象。 一些内置类型,如 Array 或 Map 拥有默认的迭代行为,而其他类型(比如Object)则没有。
当然我们要问一个问题,我们转成这样的一个东西有什么好处呢? 当一个对象变成一个可迭代对象的时候,就可以进行某些迭代操作;比如 for...of 操作时,其实就会调用它的 @@iterator 方法;
可迭代对象是一种抽象的说法。 基本上,可以把可迭代对象理解成数组或集合这样的集合类型的对象。它们包含的元素都是有限的,而且都具有无歧义的遍历顺序:不过,可迭代对象不一定是集合对象,也可以是仅仅具有类似数组行为的其他数据结构,比如上面提到的计数循环。该循环中生成的值是暂时性的,但循环本身是在执行迭代。计数循环和数组都具有可迭代对象的行为。
任何实现 Iterable 接口的数据结构都可以被实现 Iterator 接口的结构“消费”(consume)。迭代器(iterator)是按需创建的一次性对象。每个迭代器都会关联一个可迭代对象,而迭代器会暴露迭代其关联可迭代对象的 API。迭代器无须了解与其关联的可迭代对象的结构,只需要知道如何取得连续的值。 这种概念上的分离正是 Iterable 和 Iterator 的强大之处。
简单来说可迭代对象实现了迭代器,而迭代器是一次性创建的,每一个迭代器都会关联一个可迭代对象。迭代器是帮助我们对某个数据结构进行遍历的对象。
写法1
var myIterable = {
//yield关键字用来暂停和恢复一个生成器函数
//`yield`在英语里的意思就是“产出”
// 迭代器工厂函数
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
}
for (let value of myIterable) {
console.log(value);
}
// 1
// 2
// 3
// 或者
[...myIterable]; // [1, 2, 3]
写法2
const info = {
friends: ['Michael', 'Stacy', 'Andy'],
[Symbol.iterator]: function() {
let index = 0;
return {
// next: () => {
// let done = (index >= this.friends.length);
// let value = !done ? this.friends[index++] : undefined;
// return {
// done: done,
// value: value
// };
// }
next: () => {
if (index < this.friends.length) {
return {done: false, value: this.friends[index++]};
} else {
return {done: true,value: undefined};
}
}
};
}
};
2.2原生迭代对象
String、Array、Map、Set、arguments对象、NodeListt 等 DOM 集合类型
实例验证
let str = 'abc';
let arr = ['a', 'b', 'c'];
let map = new Map().set('a', 1).set('b', 2).set('c', 3);
let set = new Set().add('a').add('b').add('c');
let els = document.querySelectorAll('div');
// 这些类型都实现了迭代器工厂函数
console.log(str[Symbol.iterator]); // f values() { [native code] }
console.log(arr[Symbol.iterator]); // f values() { [native code] }
console.log(map[Symbol.iterator]); // f values() { [native code] }
console.log(set[Symbol.iterator]); // f values() { [native code] }
console.log(els[Symbol.iterator]); // f values() { [native code] }
// 调用这个工厂函数会生成一个迭代器
console.log(str[Symbol.iterator]()); // StringIterator {}
console.log(arr[Symbol.iterator]()); // ArrayIterator {}
console.log(map[Symbol.iterator]()); // MapIterator {'a' => 1, 'b' => 2, 'c' => 3}
console.log(set[Symbol.iterator]()); // SetIterator {'a', 'b', 'c'}
console.log(els[Symbol.iterator]()); // ArrayIterator {}
2.3不可迭代类型
number、Object(原生对象)、Boolean、bigint、symbol、undefined、null 其中undefined和null会报错。
let num = 1;
let obj = {a:1}; //原生对象
let bol = true;
let big = 1234567890123456789012345678901234567890n;
let sym = Symbol('sym');
let und = undefined;
let nul = null;
let fun = function(){};//随便定义一个函数是不可迭代
console.log(fun[Symbol.iterator]); // undefined
console.log(num[Symbol.iterator]); // undefined
console.log(obj[Symbol.iterator]); // undefined
console.log(bol[Symbol.iterator]); // undefined
console.log(big[Symbol.iterator]); // undefined
console.log(sym[Symbol.iterator]); // undefined
console.log(und[Symbol.iterator]); // Uncaught TypeError: undefined is not iterable (cannot read property Symbol(Symbol.iterator))
console.log(nul[Symbol.iterator]); // Uncaught TypeError: object null is not iterable (cannot read property Symbol(Symbol.iterator))
console.log(fun[Symbol.iterator]); // undefined
2.4可迭代对象应用
JavaScript中语法:for ...of、展开语法(spread syntax)、yield*(后面讲)、解构赋值(Destructuring_assignment);
创建一些对象时:new Map([Iterable])、new WeakMap([iterable])、new Set([iterable])、new WeakSet([iterable]);
一些方法的调用:Promise.all(iterable)、Promise.race(iterable)、Array.from(iterable);
接收可迭代对象的原生语言特性包括:
for-of 循环
数组解构
扩展操作符
Array.from()
创建集合
创建映射
Promise.all()接收由期约组成的可迭代对象
Promise.race()接收由期约组成的可迭代对象
yield操作符,在生成器中使用(yield 表达式用于委托给另一个generator 或可迭代对象。)
//创建自定义迭代对象
const info = {
friends: ['Michael', 'Stacy', 'Andy'],
[Symbol.iterator]: function() {
let index = 0;
return {
next: () => {
if (index < this.friends.length) {
return {done: false, value: this.friends[index++]};
} else {
return {done: true,value: undefined};
}
}
};
}
};
//或者这么创建也是等价的(结果等价类型不同)
// const info = ['Michael', 'Stacy', 'Andy'];
// console.log(info[Symbol.iterator]().next()); // { done: false, value: 'Michael' }
//for...of循环
for (let friend of info) {
console.log(friend);
}
// console.log(Array.isArray(info)); // true
//展开运算符
console.log([...info]); // [ 'Michael', 'Stacy', 'Andy' ]
//解构赋值
let [first, second, third] = info;
console.log(first, second, third); // Michael Stacy Andy
//Array.from
console.log(Array.from(info)); // [ 'Michael', 'Stacy', 'Andy' ]
//Set构造函数
console.log(new Set(info)); // Set { 'Michael', 'Stacy', 'Andy' }
//Map构造函数
console.log(new Map([[1, 'one'], [2, 'two'], [3, 'three']])); // Map { 1 => 'one', 2 => 'two', 3 => 'three' }
//Promise.all
Promise.all(info).then((values) => {
console.log(values); // [ 'Michael', 'Stacy', 'Andy' ]
});
3.自定义迭代器类及终止迭代器
与 Iterable 接口类似,任何实现 Iterator 接口的对象都可以作为迭代器使用。
终止迭代器(退出遍历一个道理) 可选的 return()方法用于指定在迭代器提前关闭时执行的逻辑。执行迭代的结构在想让迭代器知
道它不想遍历到可迭代对象耗尽时,就可以“关闭”迭代器。可能的情况包括:
for-of 循环通过 break、continue、return 或 throw 提前退出;
解构操作并未消费所有值。
return()方法必须返回一个有效的 IteratorResult 对象。简单情况下,可以只返回{ done: true }。
因为这个返回值只会用在生成器的上下文中。
class Classroom {
constructor(name, address, initialStudents) {
this.name = name
this.address = address
this.initialStudents = initialStudents
}
*[Symbol.iterator]() {
yield *this.initialStudents
// let index = 0
// return {
// next: () => {
// if (index < this.initialStudents.length) {
// return { done: false, value: this.initialStudents[index++] }
// } else {
// return { done: true, value: undefined }
// }
// },
// return() {
// console.log("迭代器提前终止")
// return { done: true }
// }
// }
}
}
const c1 = new Classroom("1101", "3幢222", ["abc", "cba", "nba", "mba"])
function foo() {
for (const stu of c1) {
if (stu === "cba") {
// throw new Error("aaaa")
// break;
// return;
continue;
}
console.log(stu)
}
}
foo()
4.其他补充
注意:object不是可迭代对象没有实现迭代器工厂函数
let obj=new Object();
console.log(obj[Symbol.iterator])//undefined
4.1for...in和for...of的区别
for…of 是ES6新增的遍历方式,允许遍历一个含有iterator接口的数据结构(数组、对象等)并且返回各项的值,和ES3中的for…in的区别如下
- for…of 遍历获取的是对象的键值,for…in 获取的是对象的键名;
- for… in 会遍历对象的整个原型链,性能非常差不推荐使用,而 for … of 只遍历当前对象不会遍历原型链;
- 对于数组的遍历,for…in 会返回数组中所有可枚举的属性(包括原型链上可枚举的属性),for…of 只返回数组的下标对应的属性值;
**总结:**for...in 循环主要是为了遍历对象而生,不适用于遍历数组;for...of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象。
4.2如何使用for...of遍历对象
for…of是作为ES6新增的遍历方式,允许遍历一个含有iterator接口的数据结构(数组、对象等)并且返回各项的值,普通的对象用for..of遍历是会报错的。
如果需要遍历的对象是类数组对象,用Array.from转成数组即可。
var obj = {
0:'one',
1:'two',
length: 2
};
obj = Array.from(obj);
for(var k of obj){
console.log(k)
}
如果不是类数组对象,就给对象添加一个[Symbol.iterator]属性,并指向一个迭代器即可。
//方法一:
var obj = {
a:1,
b:2,
c:3
};
obj[Symbol.iterator] = function(){
var keys = Object.keys(this);
var count = 0;
return {
next(){
if(count<keys.length){
return {value: obj[keys[count++]],done:false};
}else{
return {value:undefined,done:true};
}
}
}
};
for(var k of obj){
console.log(k);
}
// 方法二
var obj = {
a:1,
b:2,
c:3
};
obj[Symbol.iterator] = function*(){
var keys = Object.keys(obj);
for(var k of keys){
yield [k,obj[k]]
}
};
for(var [k,v] of obj){
console.log(k,v);
}
Day62-63【2022年9月24日-2022年9月25日】
休息
精进
真正厉害的人都有强大的内驱力,内在驱动是自我成长的动力,唯有内在驱动,才能无视世界纷扰,永远走在自我成就的路上。
参考资料
- JavaScript高级程序设计(第4版)
- MDN
- 解锁前端面试体系核心攻略
- 鲨鱼哥面试题总结
结语
志同道合的小伙伴可以加我,一起交流进步,我们坚持每日精进(互相监督思考学习,如果坚持不下来我可以监督你)。我们一起努力鸭! ——>点击这里
备注
按照时间顺序倒叙排列,完结后按时间顺序正序排列方便查看知识点,工作日更新。