Promise 应用场景
异步处理数据:如网络请求超时提醒,异步加载数据等
原理:事件轮询机制 Event Loop
浏览器处理时的先后顺序:同步任务执行完毕之后会在回调队列中轮询,有新的异步任务开始推入回调栈内进行执行。
四个执行模块:Browser Console,Call Stack, Web APIs, Callback Queue
console.log('start');
setTimeout(function cb() => {
console.log('async');
}, 1000);
console.log('end');
执行细节如下:
- console.log('start') 推入调用栈,执行完毕后清空,打印start
- 调用栈执行setTimeout,setTimeout是浏览器定义的,所以timer会在Web APIs中,参数cb,也在其中。1秒中之后把cb放入Callback Queue中
- setTimeout执行完毕,清空调用栈
- 执行最后一行代码,推到调用栈中,调用栈执行,浏览器控制台打印end
- 同步代码执行完毕,启用event loop 在回调栈中轮询任务
- 轮询到之前推入的cb,将其推入调用栈执行
- cb中的console.log('async'),推入调用栈并执行打印async
- console.log('async')执行完毕,清空;cb执行完毕,清空
宏任务队列可以有多个,微任务队列只有一个(浏览器为了能够使得JS内部(macro)task与DOM任务能够有序的执行,会在一个(macro)task执行结束后,在下一个(macro)task 执行开始前,对页面进行重新渲染)。
宏任务:script(全局任务), setInterval, setTimeout, setImmediate, I/O, UI 事件.
(消息队列,添加在执行栈的尾部)
微任务:process.nextTick, Promise, Object.observer, MutationObserver.
(作业队列, 优先级高于宏任务) 引用参考自火锅小王子00
术语
- promise 是一个有then方法的对象或函数。行为遵循本规范
- thenable 是一个then方法的对象或函数。
- value 是promise状态成功时的值resolve 的参数 类型可以时undefined / thenable promise, number, boolean
- reason 是promise 状态失败的值,reject的参数,表示拒绝的原因
- exception 是一个使用throw抛出的异常值
规范
Promise States
promise应该有三种状态:注意他们之间的流转关系
-
pending
-
初始状态,可改变
-
一个Pormise在resolve和reject之前 都处于这个状态
-
resolve函数:pending -> fulfilled
-
reject函数:pending -> rejected
Promise 的状态完全由上述两个函数控制
-
-
fulfieled
- 最终态,不可改变
- 一个Promise被resolve之后变成此状态
- 必须有个value值;默认undefined
-
rejected
- 最终态,不可改变
- 一个Promise被reject之后变成此状态
- 必须有个reason值;默认undefined
状态流转路线:
pending --> fulfilled
pending --> rejected
then
promise 应该提供一个then方法,用来访问最终结果:访问value
then方法接受两个参数:
promise.then(onFulfilled, onRejected)
-
参数要求
-
onFulfilled 必须是函数类型,如果不是函数,应该被忽略
-
onRejected 必须是函数类型,如果不是函数,应该被忽略
忽略:如果不是函数,会给一个默认的函数
-
-
onFulfilled 特性
- 在 promise 变成 fulfilled 状态时,应该调用 onFulfilled,参数是value
- 在 promise 变成 fulfilled 之前,不应该被调用
- 只能被调用一次(可能需要一个变量限制执行次数)
-
onRejected 特性
- 在 promise 变成 rejected 状态时,应该调用 onRejected,参数是reason
- 在 promise 变成 rejected 之前,不应该被调用
- 只能被调用一次(可能需要一个变量限制执行次数)
-
onFulfilled 和 onRejected 应该都是微任务
浏览器封装了queueMicrotask 来实现微任务的调用
queueMicrotask () => {}
-
then方法可以被调用多次
const promise = new Promise(); promise.then(cb1, cb2); promise.then(cb1, cb2); promise.then(cb1, cb2); promise.then(cb1, cb2);-
promise 状态变成fulfulled之后,所有的onFulfilled回调都需要按照then的顺序执行
-
promise 状态变成rejected之后,所有的onRejected回调都需要按照then的顺序执行
二者的都需要数组去存:
[onFulfilled1, onFulfilled2, onFulfilled3] [onRejected1, onRejected2, onRejected3]
-
-
then 的返回值
then返回值是一个Promise,并且是一个新的Promise
const promise1 = new Promise(); const promise2 = promise1.then(cb1, cb2) // 这里的promise2是个新的Promise // 因此,链式调用并不是返回第一个promise 的最终态,而是和最后一个then有关- onFulfilled 或者 onRejected 执行结果为x,调用resolvePromise(): 解析promise
- onFulfilled 或者 onRejected 执行时报错了,promise2就需要被reject
- onFulfilled 不是一个函数,会做透传,promise2 以 promise1 的value,触发fulfilled
- onRejected 不是一个函数,会做透传,promise2 以 promise1 的reason,触发rejected
-
resolvePromise 意义在于对promise 各种值的处理
resolvePromise(promise2, x, resolve, reject)-
如果 promise2 === x, reject typeError 这是为了防止死循环
-
如果x是一个promise
如果是pending -> promise2 也的是 pending,知道x状态变化
如果是fulfilled -> promise2 以相同的value fulfilled
如果是rejected -> promise2 以相同的reason rejected
-
如果x是一个object/function
去获取 then let then = x.then; 看是否报错
then如果是一个函数,then.call(x, resolvePromiseFn, rejectePromiseFn)
-
一步步实现一个Promise
-
初始化class
class MPromise { constructor() { } } -
定义三种状态类型
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; -
设置初始状态:
class MPromise { constructor() { this.status = PENDING; this.value = null; this.reason = null; } } -
两个改变状态的方法:resolve/reject
-
更改status,pending -> fulfilled, rejected
-
入参value/reason
class MPromise { constructor() { this.status = PENDING; this.value = null; this.reason = null; } resolve(value) { // 约束最终态不可变 if (this.status === PENDING) { this.value = value; this.status = FULFILLED; } } rejected(reason) { if (this.status === PENDING) { this.reason = reason; this.status = REJECTED; } } } -
-
promise构造函数的入参
new Promise((resolve, reject) => {})-
入参是一个函数,函数接受两个参数,resolve,reject
-
new Promise的时候,就要立即执行这个函数,并且有任何错误都要被reject出去
console.log('begin'); // 同步任务 new Promise((resolve, reject) => { console.log(1) // 同步任务,new Promise之后立即执行 }).then(() => { console.log(2) // .then才会被称为微任务 })
class MPromise { constructor(fn) { this.status = PENDING; this.value = null; this.reason = null; // 需要被立即执行 // 注意this绑定 // 一旦报错,reject try { fn(this.resolve.bind(this), this.reject.bind(this)); } catch(e) { this.reject(e) } } resolve(value) { // 约束最终态不可变 if (this.status === PENDING) { this.value = value; this.status = FULFILLED; } } rejected(reason) { if (this.status === PENDING) { this.reason = reason; this.status = REJECTED; } } } -
-
then方法,两个参数onFulfilled, onRejected,返回一个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 MPromise((resolve, reject) => { switch(this.status) { case FULFILLED: { realOnFulfilled(); break; } case REJECTED: { realOnRejected(); break; } } }); return promise2; } // 辅助函数判断入参是否为函数 isFunction(param) { return typeof param === 'function'; }以上代码只能够处理同步执行:
new Promise((resolve, reject) => { console.log(1); resolve(); }).then(value => ) // 当内部有异步的情况的话 new Promise((resolve, reject) => { console.log(1); setTimout(() => {}, 1000) // 这里还是pending }).then(value => )因此,需要数组去存;此外还因为then可以被多次调用
FULFILLED_CALLBACK_LIST = []; REJECTED_CALLBACK_LIST = [];const promise2 = new MPromise((resolve, reject) => { switch(this.status) { case FULFILLED: { realOnFulfilled(); break; } case REJECTED: { realOnRejected(); break; } case PENDING: { this.FULFILLED_CALLBACK_LIST.push(realOnFulfilled); this.REJECTED_CALLBACK_LIST.push(realOnRejected); } } }); return promise2; }状态改变时,执行回调:通过getter,setter监听状态变化
_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; } } }需要一个私有变量存改变的状态,否则死循环。
-
处理onFulfilled,onRejected的值 - 应该是微任务;返回值为x,x调用resolvePromise函数
const promise2 = new MPromise((resolve, reject) => { const fulfilledMicrotask = () => { try { const x = realOnFilfilled(this.value); this.resolvePromise(promise2, x, resolve, reject); } catch(e) { reject(e); } } const rejectedMicrotask = () => { try { realOnRejected(this.reason); this.resolvePromise(promise2, x, resolve, reject); } catch(e) { reject(e); } } 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); } } }); -
resolvePromise
const promise2 = new MPromise((resolve, reject) => { const fulfilledMicrotask = () => { // 包装queueMicrotask queueMicrotask(() => { try { // 得到x const x = realOnFilfilled(this.value); this.resolvePromise(promise2, x, resolve, reject); } catch(e) { reject(e); } }) } const rejectedMicrotask = () => { const fulfilledMicrotask = () => { // 包装queueMicrotask queueMicrotask(() => { try { // 得到x const x = realOnRejected(this.reason); this.resolvePromise(promise2, x, resolve, reject); } catch(e) { reject(e); } }) } 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); } } }); resolvePromise(promise2, x, resolve, reject) { if (promise2 === x) { return reject(new TypeError('The promise and the return value are the same!')) } // 如果x是一个promise,是否时对象 if (x instanceof MPromise) { queueMicrotask(() => { x.then( (y) => { this.resolvePromise(promise2, y, resolve, reject) }, reject ) }) } else if (typeof === 'object' || this.isFunction(x)) { if (x === null) { return resolve(x); } // 规范中提到,但是不怎么发生,提出x的then属性 let then = null; try { then = x.then; } catch(error) { return reject(error) } // 判断 then 是否是函数 if (this.isFunction(then)) { // 限制回调次数 let called = false; try { then.call( x, (y) => { if (called) { return; } called = true; this.resolvePromise(promise2, y, resolve, reject); }, (r) => { if (called) { return; } called = true; reject(r); } ) } catch(error) { if (called) { return; } reject(error); } } else { resolve(x); } } else { resolve(x); } } -
catch方法
catch(onRejected) { return this.then(null, onRejected); } -
静态方法resolve,reject
// MPromise.resolve(); // MPromise.reject(); static resolve(value) { if (value instanceof MPromise) { return value; } return new MPromise((resolve) => { resolve(value); }) } static reject(reason) { return new MPromise((resolve, reject) => { reject(reason); }) }
测试:
const test = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(111);
}, 1000);
}).then((value) => {
console.log(`[then] value = ${value}`)
}).catch((reason) => {
console.log(`[catch] reason = ${reason}`)
})
console.log(test)
setTimeout(() => {
console.log(test)
}, 2000)
关于回调函数数组储存:
-
链式调用没有意义 -->每次返回一个新的promise,因此数组为空
-
但基于如下情况的使用有意义:
test.then(() => {}); test.then(() => {}); test.then(() => {}); test.then(() => {});
关于then, catch都返回新的promise
const test = new Promise((resolve, reject) => {
setTimeout(() => {
reject(111);
}, 1000);
}).catch((reason) => {
console.log(`[报错] reason=${reason}`);
console.log(test) // status = pending
})
setTimeout(() => {
console.log(test); //status = fulfulled
}, 3000)
- catch 函数会返回一个新的promise,而test就是这个新的promise
- catch 回调里,打印test的时候,整个回调函没有执行完成,所以此时的状态时pending
- catch回调函数里,如果成功执行了,没有报错,那么会改变整个新的Promise状态为fulfilled
生成器简介
特征:
-
命名:function* generator() {}
-
可中断,可恢复
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}
async 简介
异步的操作同步书写
function longTimeFn(time) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(time);
}, time)
})
}
async function test() {
console.log(new Date().toLocaleTimeString());
await longTimeFn(1000);
console.log(new Date().toLocaleTimeString());
}
test();