Promise 规范及应用
主讲:路白 && 云隐
课程目标
- 进程和线程
Promise A+规范- 实现
Promise - 常见问题
// 什么是异步???
// 异步执行
let count = 1;
let timer = setTimeout(() => {
count++;
console.log('in', count);
}, 1000);
console.log('out', count);
// 循环执行 + 终止
let count = 1;
let timer = setInterval(() => {
count++;
console.log('in', count);
}, 1000);
console.log('out', count);
setTimeout(() => {
clearInterval(timer);
console.log('in', count);
}, 5000);
// 看不见的队列,存放着他需要默默执行的命令
进程和线程
概念与区别
进程是
CPU资源分配的最小单位,线程是CPU调度的最小单位。
概念
- 进程:是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竞争计算机系统资源的基本单位;
- 线程:是进程的一个执行单元,是进程内科调度实体,比进程更小的独立运行的基本单位,线程也被称为轻量级进程;
- 协程:是一种比线程更加轻量级的存在,一个线程也可以拥有多个协程,其执行过程更类似于子例程,或者说不带返回值的函数调用;
进程和线程的区别
- 地址空间:线程共享本进程的地址空间,而进程之间是独立的地址空间;
- 资源:线程共享本进程的资源如内存、
I/O、CPU等,不利于资源的管理和保护,而进程之间的资源是独立的,能很好的进行资源管理和保护; - 健壮性:多进程要比多线程健壮,一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉;
- 执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口,执行开销大,但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,执行开销小;
- 可并发性:两者均可并发执行;
面试题
- 映射到前端:浏览器,例如:
Chrome浏览器新开一个窗口,tab页是进程还是线程?- 是进程(回答问题的时候要从区别概念上出发)
- 发散点:
- 方向一:窗口(进程间)通信?浏览器的存储 -
storage(localStorage)、cookie=> 多种存储的区别 - 方向二:浏览器原理(中高级岗位居多)- 参考图
- 进程只是起到调度、实施、整理的作用;
- 实际执行的时候是通过线程的;
- 方向一:窗口(进程间)通信?浏览器的存储 -
原理图:www.processon.com/diagraming/…
EVENT-LOOP
执行栈
JS单线程语言,单步执行
function run() {
func1();
}
function func1() {
func2();
}
function func2() {
throw new Error('plz check ur call stack');
}
run();
面试题
JS堆栈的执行顺序与堆栈溢出 => 引申到性能优化
// 爆栈 Uncaught RangeError: Maximum call stack size exceeded
function func() {
func();
}
func();
- 执行顺序题
setTimeout(() => {
// 异步 macro
console.log('Time out');
});
Promise.resolve(1).then(() => {
// 异步 micro
console.log('promise');
});
console.log('hi'); // 同步 macro
// hi promise Time out
// 追问:为什么?怎么思考的
Promise A+ 规范
讲解 PromiseA+ 规范前,咱们先来了解一下这些术语,以便在后续提到的时候有明确且统一的概念。
术语
promise是一个有then方法的对象或者是函数,行为遵循本规范;new Promise执行器executor(),执行器参数是? -resolve、rejectthenable是一个有then方法的对象或者是函数;value是promise状态成功时的值,也就是resolve的参数,包括各种数据类型,也包括undefined/thenable/promise;reason是promise状态失败时的值,也就是reject的参数,表示拒绝的原因;exception是一个使用throw抛出的异常值;
规范
接下来分几部分来讲解 PromiseA+ 规范。
Promise States
promise 应该有三种状态,要注意他们之间的流转关系:
1、pending
- 初始的状态,可改变;
- 一个
promise在resolve或者reject前都处于这个状态; - 可以通过
resolve -> fulfilled状态; - 可以通过
reject -> rejected状态;
2、fulfilled
- 最终态,不可变;
- 一个
promise被resolve之后会变成这个状态; - 必须拥有一个
value值;如果resolve()不传值,那就是undefined;
3、rejected
- 最终态,不可变;
- 一个
promise被reject之后会变成这个状态; - 必须拥有一个
reason;
Tips:总结一下,就是promise的状态流转是这样的:
pending -> resolve(value) -> fulfilled
pending -> reject(reason) -> rejected
then
promise 应该提供一个 then 方法,用来访问最终的结果,无论是 value 还是 reason;
const promise = new Promise();
promise.then(onFulfilled, onRejected);
1、参数要求
onFulfilled必须是函数类型,如果不是函数,应该被 忽略;onRejected必须是函数类型,如果不是函数,应该被 忽略;
2、onFulfilled 特性
- 在
promise变成fulfilled时,应该调用onFulfilled,参数是value; - 在
promise变成fulfilled之前,不应该被调用; - 只能被调用一次(所以在实现的时候需要一个变量来限制执行次数);
3、onRejected 特性
- 在
promise变成rejected时,应该调用onRejected,参数是reason; - 在
promise变成rejected之前,不应该被调用; - 只能被调用一次(所以在实现的时候需要一个变量来限制执行次数);
4、onFulfilled 和 onRejected 应该是微任务
这里用 queueMicrotask() 来实现微任务的调用。
5、then 方法可以被调用多次
const promise = new Promise();
promise.then(cb1, cb2);
promise.then(cb1, cb2);
promise.then(cb1, cb2);
promise.then(cb1, cb2);
promise状态变成fulfilled后,所有的onFulfilled回调都需要按照then的顺序执行,也就是按照注册顺序执行(所以在实现的时候需要一个数组来存放多个onFulfilled的回调);promise状态变成rejected后,所有的onRejected回调都需要按照then的顺序执行,也就是按照注册顺序执行(所以在实现的时候需要一个数组来存放多个onRejected的回调);
6、返回值
then 应该返回一个 promise,一个新的 promise;
const promise1 = new Promise();
const promise2 = promise1.then(onFulfilled, onRejected);
onFulfilled或onRejected执行的结果为x,调用resolvePromise(这里大家可能难以理解,可以先保留疑问,下面详细讲一下resolvePromise是什么东西);- 如果
onFulfilled或者onRejected执行时抛出异常e,promise2需要被reject; - 如果
onFulfilled不是一个函数,promise2以promise1的value触发fulfilled; - 如果
onRejected不是一个函数,promise2以promise1的reason触发rejected;
7、resolvePromise
resolvePromise(promise2, x, resolve, reject);
-
如果
promise2和x相等(promise2 === x),那么抛出异常reject TypeError; -
如果
x是一个promise:- 如果
x是pending状态,那么promise必须要在pending状态等待,直到x变成fulfilled或者rejected; - 如果
x被fulfilled,fulfill promise with the same value; - 如果
x被rejected,reject promise with the same reason;
- 如果
-
如果
x是一个object或者是一个function:let then = x.then;- 如果
x.then这步出错,那么reject promise with the same reason; - 如果
then是一个函数,then.call(x, resolvePromise, rejectPromise) => x.then();resolvePromise的入参是y,执行resolvePromise(promise2, y, resolve, reject);rejectPromise的入参是r,reject promise with r- 如果
resolvePromise和rejectPromise都调用了,那么第一个调用优先,后面的调用忽略; - 如果调用
then抛出异常e:- 如果
resolvePromise或rejectPromise已经被调用,那么忽略 - 则,
reject promise with e as the reason
- 如果
- 如果
then不是一个function,fulfill promise with x
- 如果
这段描述看起来非常的空洞乏味,最重要的是看不懂! 所以待会实现代码的时候,同学们注意一下 resolvePromise 函数具体的实现,结合代码来看会好很多。
一步步实现一个 Promise
平常用 promise 的时候,是通过 new 关键字来 new Promise(),所以咱们应该用构造函数或者 class 来实现。都 2021 年了,咱们就用 class 来实现一下吧。
1、初始化 class
class MyPromise {
constructor() {}
}
2、定义三种状态类型
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
3、设置初始状态和值
class MyPromise {
constructor() {
// 设置初始状态和值
// 初始状态为 pending
this.status = PENDING;
this.value = null;
this.reason = null;
}
}
4、resolve 和 reject 方法
- 根据刚才的规范,这两个方法是要更改
status的,从pending改到fulfilled/rejected; - 注意:两个函数的入参分别是
value和reason;
class MyPromise {
constructor() {
// 设置初始状态和值
// 初始状态为 pending
this.status = PENDING;
this.value = null;
this.reason = null;
}
resolve(value) {
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED;
}
}
reject(reason) {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
}
}
}
5、promise 构造函数的入参
是不是发现咱们的 promise 少了入参,咱们来加一下;
- 入参是一个函数,函数接收
resolve和reject两个参数; - 注意:在初始化
promise的时候(new Promise()),就要执行这个函数,并且有任何报错都要通过reject抛出去;
class MyPromise {
constructor(executor) {
// 设置初始状态和值
// 初始状态为 pending
this.status = PENDING;
this.value = null;
this.reason = null;
try {
executor(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error);
}
}
resolve(value) {
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED;
}
}
reject(reason) {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
}
}
}
6、then 方法
then接收两个参数,onFulfilled和onRejected;
then(onFulfilled, onRejected) {}
- 检查并处理参数,之前提到的如果不是
function,就 忽略,这个忽略指的是原样返回value或者reason(也就是“透传”);
then(onFulfilled, onRejected) {
const realOnFulfilled = this.isFunction(onFulfilled)
? onFulfilled
: value => {
return value;
};
const realOnRejected = this.isFunction(onRejected)
? onRejected
: reason => {
throw reason;
};
}
isFunction(param) {
return typeof param === 'function';
}
- 要知道
.then的返回值整体是一个promise,所以咱们先用promise来包裹一下,其他逻辑待会再实现;
then(onFulfilled, onRejected) {
const realOnFulfilled = this.isFunction(onFulfilled)
? onFulfilled
: value => {
return value;
};
const realOnRejected = this.isFunction(onRejected)
? onRejected
: reason => {
throw reason;
};
const promise2 = new MyPromise((resolve, reject) => {});
return promise2;
}
- 根据当前
promise的状态,调用不同的函数;
then(onFulfilled, onRejected) {
const realOnFulfilled = this.isFunction(onFulfilled)
? onFulfilled
: value => {
return value;
};
const realOnRejected = this.isFunction(onRejected)
? onRejected
: reason => {
throw reason;
};
const promise2 = new MyPromise((resolve, reject) => {
switch (this.status) {
case FULFILLED: {
realOnFulfilled(this.value);
break;
}
case REJECTED: {
realOnRejected(this.reason);
break;
}
}
});
return promise2;
}
-
此时到这里,所有的代码执行都是同步的,这个时候有的同学要问了,你这样写,是在
then函数被调用的瞬间就会执行,那这时候如果status还没变成fulfilled或者rejected怎么办,很有可能还是pending的。所以我们需要一个状态的监听机制,当状态变成fulfilled或者rejected后,再去执行callback;- 那么我们首先要拿到所有的
callback,然后才能在某个时机去执行他,新建两个数组,来分别存储成功和失败的回调,调用then的时候,如果还是pending就存入数组;
class MyPromise { FULFILLED_CALLBACK_LIST = []; REJECTED_CALLBACK_LIST = []; constructor(executor) { // ... } // ... then(onFulfilled, onRejected) { const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : value => { return value; }; const realOnRejected = this.isFunction(onRejected) ? onRejected : reason => { throw reason; }; const promise2 = new MyPromise((resolve, reject) => { switch (this.status) { case FULFILLED: { realOnFulfilled(this.value); break; } case REJECTED: { realOnRejected(this.reason); break; } case PENDING: { this.FULFILLED_CALLBACK_LIST.push(realOnFulfilled); this.REJECTED_CALLBACK_LIST.push(realOnRejected); } } }); return promise2; } }- 在
status发生变化的时候,就执行所有的回调,这里咱们用一下ES6的getter和setter,这样更符合语义,当status改变时,去做什么事情(当然也可以顺序执行,在给status赋值后,下面再加一行forEach);
_status = PENDING; get status() { return this._status; } set status(newStatus) { this._status = newStatus; switch (newStatus) { case FULFILLED: { this.FULFILLED_CALLBACK_LIST.forEach(callback => callback(this.value)); break; } case REJECTED: { this.REJECTED_CALLBACK_LIST.forEach(callback => callback(this.reason)); break; } } } - 那么我们首先要拿到所有的
7、then 的返回值
上面只是简单说了下 then 的返回值是一个 Promise,那么接下来具体讲一下返回 promise 的 value 和 reason是什么。
- 如果
onFulfilled或者onRejected抛出一个异常e,则promise2必须拒绝执行,并返回拒因e(这样的话,我们就需要手动catch代码,遇到报错就reject);
then(onFulfilled, onRejected) {
const realOnFulfilled = this.isFunction(onFulfilled)
? onFulfilled
: value => {
return value;
};
const realOnRejected = this.isFunction(onRejected)
? onRejected
: reason => {
throw reason;
};
const promise2 = new MyPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
try {
realOnFulfilled(this.value);
} catch (error) {
reject(error);
}
};
const rejectedMicrotask = () => {
try {
realOnRejected(this.reason);
} catch (error) {
reject(error);
}
};
switch (this.status) {
case FULFILLED: {
fulfilledMicrotask();
break;
}
case REJECTED: {
rejectedMicrotask();
break;
}
case PENDING: {
this.FULFILLED_CALLBACK_LIST.push(fulfilledMicrotask);
this.REJECTED_CALLBACK_LIST.push(rejectedMicrotask);
}
}
});
return promise2;
}
- 如果
onFulfilled不是函数且promise1成功执行,promise2必须成功执行并返回相同的值; - 如果
onRejected不是函数且promise1拒绝执行,promise2必须拒绝执行并返回相同的据因;
需要注意的是,如果 promise1 的 onRejected 执行成功了,promise2 应该被 resolve,这里咱们其实已经在参数检查的时候做过了,也就是这段代码:
const realOnFulfilled = this.isFunction(onFulfilled)
? onFulfilled
: value => {
return value;
};
const realOnRejected = this.isFunction(onRejected)
? onRejected
: reason => {
throw reason;
};
- 如果
onFulfilled或者onRejected返回一个值x,则运行resolvePromise方法;
then(onFulfilled, onRejected) {
const realOnFulfilled = this.isFunction(onFulfilled)
? onFulfilled
: value => {
return value;
};
const realOnRejected = this.isFunction(onRejected)
? onRejected
: reason => {
throw reason;
};
const promise2 = new MyPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
try {
const x = realOnFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
};
const rejectedMicrotask = () => {
try {
const x = realOnRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
};
switch (this.status) {
case FULFILLED: {
fulfilledMicrotask();
break;
}
case REJECTED: {
rejectedMicrotask();
break;
}
case PENDING: {
this.FULFILLED_CALLBACK_LIST.push(fulfilledMicrotask);
this.REJECTED_CALLBACK_LIST.push(rejectedMicrotask);
}
}
});
return promise2;
}
8、resolvePromise
resolvePromise(promise2, x, resolve, reject) {
// 如果 newPromise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 newPromise
// 这是为了防止死循环
if (promise2 === x) {
return reject(new TypeError('The promise and the return value are the same'));
}
if (x instanceof MyPromise) {
// 如果 x 为 Promise ,则使 newPromise 接受 x 的状态
// 也就是继续执行 x,如果执行的时候拿到一个 y,还要继续解析 y
queueMicrotask(() => {
x.then(y => {
this.resolvePromise(promise2, y, resolve, reject);
}, reject);
});
} else if (typeof x === 'object' || this.isFunction(x)) {
// 如果 x 为对象或者函数
if (x === null) {
// null 也会被判断为对象
return resolve(x);
}
let then = null;
try {
// 把 x.then 赋值给 then
then = x.then;
} catch (error) {
// 如果取 x.then 的值时抛出错误 e,则以 e 为据因拒绝 promise
return reject(error);
}
// 如果 then 是函数
if (this.isFunction(then)) {
let called = false;
// 将 x 作为函数的作用域 this 调用
// 传递两个回调函数作为参数,第一个参数叫做 resolvePromise,第二个参数叫做 rejectPromise
try {
then.call(
x,
// 如果 resolvePromise 以值 y 为参数被调用,则运行 resolvePromise
y => {
// 需要有一个变量 called 来保证只调用一次
if (called) return;
called = true;
this.resolvePromise(promise2, y, resolve, reject);
},
// 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
r => {
if (called) return;
called = true;
reject(r);
}
);
} catch (error) {
// 如果调用 then 方法抛出了异常 e
if (called) return;
// 否则以 e 为据因拒绝 promise
reject(error);
}
} else {
// 如果 then 不是函数,以 x 为参数执行 promise
resolve(x);
}
} else {
// 如果 x 不为对象或者函数,以 x 为参数执行 promise
resolve(x);
}
}
9、onFulfilled 和 onRejected 是微任务
咱们可以用 queueMicrotask 包裹执行函数;
const fulfilledMicrotask = () => {
// 使用 queueMicrotask 微任务包裹一下
queueMicrotask(() => {
try {
const x = realOnFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
};
const rejectedMicrotask = () => {
// 使用 queueMicrotask 微任务包裹一下
queueMicrotask(() => {
try {
const x = realOnRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
};
10、简单写点代码测试一下
const test = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(111);
}, 1000);
}).then(console.log);
console.log(test);
setTimeout(() => {
console.log(test);
}, 2000);
/*
MyPromise {
FULFILLED_CALLBACK_LIST: [],
REJECTED_CALLBACK_LIST: [],
_status: 'pending',
value: null,
reason: null
}
111
MyPromise {
FULFILLED_CALLBACK_LIST: [],
REJECTED_CALLBACK_LIST: [],
_status: 'fulfilled',
value: undefined,
reason: null
}
*/
11、catch 方法
catch(onRejected) {
return this.then(null, onRejected);
}
12、promise.resolve
将现有对象转为 Promise 对象,如果 Promise.resolve 方法的参数不是具有 then 方法的对象(又称 thenable 对象),则返回一个新的 Promise 对象,且它的状态为 fulfilled。
注意:这是一个静态方法,因为咱们是通过 Promise.resolve 调用的,而不是通过实例去调用的。
static resolve(value) {
if (value instanceof MyPromise) {
return value;
}
return new MyPromise(resolve => {
resolve(value);
});
}
13、promise.reject
返回一个新的 Promise 实例,该实例的状态为 rejected,Promise.reject 方法的参数 reason,会被传递给实例的回调函数。
static reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason);
});
}
14、promise.race
const p = Promise.race([p1, p2, p3]);
该方法是将多个 Promise 实例,包装成一个新的 Promise 实例。
只要 p1、p2、p3 之中有一个实例率先改变状态,p 的状态就跟着改变,那个率先改变的 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);
}
);
}
}
});
}
15、写段测试代码
const test = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(111);
}, 3000);
});
const test2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(222);
}, 2000);
});
const test3 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(333);
}, 1000);
});
MyPromise.race([test, test2, test3]).then(console.log);
/*
333
*/
16、promise.all
static all(promiseList) {
return new MyPromise((resolve, reject) => {
let res = [];
let count = 0;
const length = promiseList.length;
for (let i = 0; i < length; i++) {
MyPromise.resolve(promiseList[i])
.then(value => {
res[i] = value;
count++;
if (count === length) {
resolve(res);
}
})
.catch(reason => {
reject(reason);
});
}
});
}
17、finally 方法
finally 方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是 fulfilled 还是 rejected。这表明,finally 方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。
finally 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
finally(callback) {
return this.then(
value => {
return MyPromise.resolve(callback()).then(() => value);
},
err => {
return MyPromise.resolve(callback()).then(() => {
throw err;
});
}
);
}
几个问题
为什么 promise resolve 了一个 value,最后输出的 value 值确是 undefined
const test = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(111);
}, 1000);
}).then(value => {
console.log('then');
// 同这样
// return undefined;
});
setTimeout(() => {
console.log(test);
}, 3000);
/*
then
MyPromise {
FULFILLED_CALLBACK_LIST: [],
REJECTED_CALLBACK_LIST: [],
_status: 'fulfilled',
value: undefined,
reason: null
}
*/
答案:
因为现在这种写法,相当于在 .then 里 return undefined,所以最后的 value 是 undefined;
如果显式 return 一个值,就不是 undefined 了;比如 return value;
.then 返回的是一个新 Promise,那么原来 promise 实现的时候,用数组来存回调函数有什么意义?
这个问题提出的时候,应该是有一个假定条件,就是链式调用的时候;
这个时候,每一个 .then 返回的都是一个新 promise,所以每次回调数组 FULFILLED_CALLBACK_LIST 都是空数组;
针对这种情况,确实用数组来存储回调没意义,完全可以就用一个变量来存储;
const test = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(111);
}, 1000);
})
.then(value => {})
.then(() => {});
但是还有一种 promise 使用的方式,这种情况下,promise 实例是同一个,数组的存在就有了意义;
const test = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(111);
}, 1000);
});
test.then(() => {});
test.then(() => {});
test.then(() => {});
test.then(() => {});
为什么我在 catch 的回调里,打印 promise,显示状态是 pending
const test = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject(111);
}, 1000);
}).catch(reason => {
console.log(`[报错] reason = ${reason}`);
console.log(test);
});
setTimeout(() => {
console.log(test);
}, 3000);
/*
[报错] reason = 111
MyPromise {
FULFILLED_CALLBACK_LIST: [],
REJECTED_CALLBACK_LIST: [],
_status: 'pending',
value: null,
reason: null
}
MyPromise {
FULFILLED_CALLBACK_LIST: [],
REJECTED_CALLBACK_LIST: [],
_status: 'fulfilled',
value: undefined,
reason: null
}
*/
catch函数会返回一个新的promise,而test就是这个新promise;catch的回调里,打印promise的时候,整个回调还并没有执行完成(所以此时的状态是pending),只有当整个回调完成了,才会更改状态;catch的回调函数,如果成功执行完成了,会改变这个新Promise的状态为fulfilled;
Generator 和 Async 简介
在讲 Generator 之前,咱们要来先了解另外一个概念,迭代器。
迭代器 Iterator
迭代器 Iterator 是 ES6 引入的一种新的遍历机制,同时也是一种特殊对象,它具有一些专门为迭代过程设计的专有接口。
每个迭代器对象都有一个 next() 方法,每次调用都返回一个当前结果对象。当前结果对象中有两个属性:
-
value:当前属性的值; -
done:用于判断是否遍历结束,当没有更多可返回的数据时,返回true;
每调用一次 next() 方法,都会返回下一个可用的值,直到遍历结束。
生成器 Generator
生成器是一种返回迭代器的函数,通过 function 关键字后的星号(*)来表示,函数中会用到新的关键字 yield。星号可以紧挨着 function 关键字,也可以在中间添加一个空格。
function longTimeFn(time) {
return new Promise(resolve => {
setTimeout(() => {
resolve(time);
}, time);
});
}
function* generator() {
const list = [1, 2, 3];
for (let i of list) {
yield i;
}
}
let g = generator();
console.log(g.next()); // { value: 1, done: false }
console.log(g.next()); // { value: 2, done: false }
console.log(g.next()); // { value: 3, done: false }
console.log(g.next()); // { value: undefined, done: true }
特性
-
每当执行完一条
yield语句后函数就会自动停止执行,直到再次调用next(); -
yield关键字只可在生成器内部使用,在其他地方使用会导致程序抛出错误; -
可以通过函数表达式来创建生成器,但是不能使用箭头函数;
let generator = function *(){}
Async 和 Await
这个就比较简单了,非常常用,就不多赘述了。
但是同学们想不想知道怎么封装一个函数,能够让 generator 自动执行到完毕?
function longTimeFn(time) {
return new Promise(resolve => {
setTimeout(() => {
resolve(time);
}, time);
});
}
function asyncFunc(generator) {
const iterator = generator();
// 接下来要执行 next
// data 为第一次执行之后的返回结果,用于传给第二次执行
const next = data => {
// 第二次执行,并接收第一次的请求结果 value 和 done
const { value, done } = iterator.next(data);
// 执行完毕, 直接返回
if (done) return;
// 第一次执行 next 时,yield 返回的 promise 实例赋值给了 value
value.then(data => {
// 当第一次 value 执行完毕且成功时,执行下一步(并把第一次的结果传递下一步)
next(data);
});
};
next();
}
asyncFunc(function* () {
let data = yield longTimeFn(1000);
console.log(data);
data = yield longTimeFn(2000);
console.log(data);
return data;
});
补充知识点
Promise 的四个静态方法的使用和区别
先提出几个问题
Promise.all与Promise.allSettled区别?Promise.race和Promise.any的运行机制?两者有什么区别?- 四兄弟只能接受 数组 作为参数吗?
- 四兄弟方法我们应该如何优雅完美的实现?
Promise.all
const p = Promise.all([p1, p2, p3]);
Promise.all 方法接受一个数组做参数,p1、p2、p3 都是 Promise 实例。如果不是 Promise 实例,则会先调用 Promise.resolve 方法将参数先转化为 Promise 实例,之后进行下一步处理。
返回值 p 的状态由 p1、p2、p3 决定,可以分成两种情况:
- 只有
p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数; - 只要
p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数;
总结:
p状态由参数执行结果决定,全部成功则返回成功,存有一个失败则失败;- 参数为非
Promise实例,会通过Promise.resolve转化成Promise实例; - 成功后返回一个数组,数组内数据按照参数顺序排列;
- 短路效应:只会返回第一个失败信息;
Promise.allSettled
Promise.allSettled() 方法接受一个数组作为参数,数组的每个成员都是一个 Promise 对象,并返回一个新的 Promise 对象。只有等到参数数组的所有 Promise 对象都发生状态变更(不管是 fulfilled 还是 rejected),返回的 Promise 对象才会发生状态变更。
总结:
allSettled方法只会成功,不会失败;- 返回结果每个成员为对象,对象的格式固定:
- 如果
promise成功,对象属性值status: fulfilled,value记录成功值; - 如果
promise失败,对象属性值status: rejected,reason记录失败原因;
- 如果
allSettled方法也可以接受Iterator类型参数;
allSettled方法与all方法最大的区别在于两点:
allSettled方法没有失败情况;allSettled方法返回有固定格式;
Promise.race
Promise.race() 方法同样是接收多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
上面案例中,只要 p1、p2、p3 之中有一个实例率先改变状态,p 的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 p 的回调函数。
Promise.any
ES2021 引入了 Promise.any() 方法。该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。
any 方法与 race 方法很像,也存在短路特性,只要有一个实例变成 fulfilled 状态,就会返回成功的结果;如果全部失败,则返回失败情况。
总结:
any方法也可以接受Iterator格式参数;- 当一个
promise实例转变为fulfilled时,any返回成功的promise,值为最早成功的promise值; - 当
promise全部失败时,any返回失败的promise,值固定为AggregateError: All promises were rejected;
窗口(进程间)通信
四种实现浏览器标签页数据通信方式:
cookielocalStoragewebworkerwebsocket
cookie
由于更新 cookie 并不能触发任何事件,因此我们需要通过定时器 setInterval 来主动监听 cookie 中的值是否改变。
存在的问题:定时器存在时间差,点击后有一定的延迟,cookie 本身的弊端;
localStorage
localStorage 也是浏览器多个页面共用的存储空间;而且 localStorage 在一个页面中添加、修改或者删除时,都会在 非当前页面中 被动触发一个 storage 事件,我们通过在其他页面中监听 storage 事件,即可拿到 storage 更新前后的值:
window.addEventListener('storage', () => {
// ...
});
实现 Ajax
open(method, url, async)方法需要三个参数:method:发送请求所使用的方法(GET或POST);与POST相比,GET更简单也更快,并且在大部分情况下都能用;然而,在以下情况中,请使用POST请求:- 无法使用缓存文件(更新服务器上的文件或数据库)
- 向服务器发送大量数据(
POST没有数据量限制) - 发送包含未知字符的用户输入时,
POST比GET更稳定也更可靠
url:规定服务器端脚本的URLasync:规定应当对请求进行异步(true)或同步(false)处理;true是在等待服务器响应时执行其他脚本,当响应就绪后对响应进行处理;false是等待服务器响应再执行
send()方法可将请求送往服务器;onreadystatechange:存有处理服务器响应的函数,每当readyState改变时,onreadystatechange函数就会被执行;readyState:存有服务器响应的状态信息:0:请求未初始化(代理被创建,但尚未调用open方法)1:服务器连接已建立(open方法已经被调用)2:请求已接收(send方法已经被调用,并且头部和状态已经可获得)3:请求处理中(下载中,responseText属性已经包含部分数据)4:请求已完成,且响应已就绪(下载操作已完成)
responseText:获得字符串形式的响应数据;setRequestHeader():POST传数据时,用来添加HTTP头,然后send(data),注意data格式;GET发送信息时直接加参数到url上就可以;
var Ajax = {
get: function (url, callback) {
// XMLHttpRequest 对象用于在后台与服务器交换数据
var xhr = new XMLHttpRequest();
xhr.open('GET', url, false);
xhr.onreadystatechange = function () {
// readyState === 4 说明请求已完成
if (xhr.readyState === 4) {
if (xhr.status == 200 || xhr.status === 304) {
console.log(xhr.responseText);
callback(xhr.responseText);
}
}
};
xhr.send();
},
post: function (url, data, callback) {
var xhr = new XMLHttpRequest();
xhr.open('POST', url, false);
// 添加 http 头,发送信息至服务器时内容编码类型
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200 || xhr.status === 304) {
console.log(xhr.responseText);
callback(xhr.responseText);
}
}
};
xhr.send(data);
}
};