JavaScript探秘-手写 Promise
关于 Promise
promise 是异步操作的一个解决方案,相对于传统的回调函数来讲更合理,可以避免回调地狱。 promise 类似于一个容器,获取异步操作的消息,存储异步操作的结果,为各种异步操作提供了统一的 API。 promise 在 ES6 中成为语言标准,原生提供了 Promise 的对象。
promise 的优缺点
优点
● 对象的状态不受外界的影响,有三种状态,pending(进行中)、fulfilled(已成功)、rejected(已失败),只有异步操作的结果可以决定当前是哪种状态。 ● 一旦状态更改就不会再改变,任何时候都可以得到这个结果。promise 的状态变化只有两种可能-从 pending 到 fulfilled、从 pending 到 rejected,当改变已发生,后续无论添加什么样的回调函数,都会返回同样的结果。 ● promise 对象可以将异步操作通过链式调用的方式以同步操作的效果运行,避免回调地狱,让代码更直观。 ● promise 对象提供统一的接口,操作异步操作更容易。
缺点
● 无法取消 promise,一旦新建就会立即执行,不能中途取消。 ● 若无回调函数,内部抛出的错误无法反应到外部。 ● 处于 pending 状态时,无法得知当前处于哪一个状态。
使用 promise
new Promise()创建 promise 对象
let promise = new Promise(function(resolve,reject){ if(/*异步操作成功*/) { resolve(value) // 成功调用resolve 往下传递参数 且只接受一个参数 }else { reject(error) // 失败调用reject 往下传递参数 且只接受一个参数 }});
promise.then(res=>{ console.log(res)}).catch(err=>{ console.log(err)})Promise.resolve/Promise.reject 创建 promise 对象 有时需要将现有对象转为 Promise 对象,Promise.resolve()、Promise.reject()方法就起到这个作用。 Promise.resolve()方法返回一个新的 Promise 实例,该实例的状态为 resolved。 Promise.reject()方法返回一个新的 Promise 实例,该实例的状态为 rejected。
● 如果参数是一个 promise 实例,将不做任何修改,原封不动的返回这个实例。 ● 如果参数是具有 then()方法的对象,会将这个对象转为 promise 对象,然后立即执行 then()方法。 ● 如果参数不是对象或者不是具有 then()方法的对象,则返回一个新的 promise 对象,状态为 resolved/rejected。 ● 如果不带有任何参数,则直接返回一个 resolved/rejected 状态的 promise 对象。
Promise 的其他方法
Promise.prototype.then() then 方法返回的是一个新的 Promise 实例(注意,不是原来那个 Promise 实例)。因此可以采用链式写法,即 then 方法后面再调用另一个 then 方法。
promise .then(function (res) { // ... }) .then(function (res) {});Promise.prototype.catch() Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
promise .then(function (posts) { // ... }) .catch(function (error) {});Promise.prototype.finally() finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
promise.then(result => {···}).catch(error => {···}).finally(() => {···});promise.finally(() => { // 语句});
// 等同于promise.then( (result) => { // 语句 return result; }, (error) => { // 语句 throw error; });Promise.all() Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。 const promise = Promise.all([promise1, promise2, promise3]); 该方法只要有一个 promise 方法返回 rejected 状态,则直接返回 rejected 状态,返回第一个返回 rejected 状态的实例的回调结果。
Promise.race() Promise.race()方法是将多个 Promise 实例,包装成一个新的 Promise 实例。 const promise = Promise.race([promise1, promise2, promise3]); 上面代码中,只要有一个 promise 实例率先改变状态,就返回率先改变状态的实例的回调结果。
Promise.allSettled() Promise.allSettled()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。 const promise = Promise.allSettled([promise1, promise2, promise3]); 该方法等待全部 promise 实例改变 resolved/rejected 状态后,输出所有实例的回调结果。
手写 Promise 和相关函数
手写 Promise
手写思路
- Promise 是一个类, 类中需要传入一个 executor 执行器
- promise 内部会提供两个方法,这两个方法会传给用户,可以更改 promise 的状态
- promise 有三个状态-等待(PENDING)、成功(RESOLVED)(返回成功的结果或 undefined)、失败(REJECTED)(返回失败的原因或 undefined)
- promise 只会从等待变为成功或者从等待变为失败。
- 每个 promise 实例上都有一个 then 方法, 分别是成功和失败的回调。
const PENDING = "PENDING";const RESOLVED = "RESOLVED";const REJECTED = "REJECTED";
function resolvePromise(promise, x, resolve, reject) { if (promise === x) { return reject( new TypeError( "[TypeError: Chaining cycle detected for promise #<Promise>]----" ) ); } let called; if ((typeof x === "object" && x != null) || typeof x === "function") { try { let then = x.then; if (typeof then === "function") { then.call( x, (y) => { if (called) return; called = true; resolvePromise(promise, y, resolve, reject); }, (r) => { if (called) return; called = true; reject(r); } ); } else { resolve(x); } } catch (e) { if (called) return; called = true; reject(e); } } else { resolve(x); }}
class Promise { constructor(executor) { this.status = PENDING; this.value = undefined; this.error = undefined; this.fulfilledList = []; this.rejectedList = []; try { executor(this.resolve.bind(this), this.reject.bind(this)); } catch (err) { this.reject(err); } }
resolve(value) { if (value instanceof Promise) { return value.then(resolve, reject); }
setTimeout(() => { if (this.status === PENDING) { this.status = RESOLVED; this.value = value; this.fulfilledList.forEach((cb) => cb(this.value)); } }, 0); }
reject(err) { setTimeout(() => { if (this.status === PENDING) { this.status = REJECTED; this.error = err; this.rejectedList.forEach((cb) => cb(this.error)); } }, 0); }
then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (value) => value; onRejected = typeof onRejected === "function" ? onRejected : (err) => { throw err; }; let promise = new Promise((resolve, reject) => { if (this.status === RESOLVED) { setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise, x, resolve, reject); } catch (err) { reject(err); } }, 0); } if (this.status === REJECTED) { setTimeout(() => { try { let x = onRejected(this.error); resolvePromise(promise, x, resolve, reject); } catch (err) { reject(err); } }, 0); } if (this.status === PENDING) { this.fulfilledList.push(() => { setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise, x, resolve, reject); } catch (err) { reject(err); } }, 0); }); this.rejectedList.push(() => { setTimeout(() => { try { let x = onRejected(this.error); resolvePromise(promise, x, resolve, reject); } catch (err) { reject(err); } }, 0); }); } }); return promise; }
catch(errCallback) { return this.then(null, errCallback); }}
let promise = new Promise((resolve, reject) => { setTimeout(() => { //reject('xxx') resolve("xxx"); }, 1000);}) .then( (res) => { console.log("成功的结果 1", res); return res; }, (error) => { console.log("失败的结果 1", error); } ) .then( (res) => { console.log("成功的结果 2", res); }, (error) => { console.log("失败的结果 2", error); } );手写 Promise.all
手写思路
- 接受一个参数,该参数是含有 promise 实例的数组
- 遍历传入的参数,用 promise.resolve 将参数转为 promise 对象
- 设置 flag,每当一个 promise 对象回调成功,flag+1,然后将回调成功结果添加到回调结果数组中。
- 当所有 promise 对象回调成功,返回回调结果数组,只要有一个 promise 对象回调失败,则触发失败状态,该回调失败的 promise 对象的错误信息将作为 promise.all 的错误信息。
function AllPromise(promiseArray) { return new Promise((resolve, reject) => { if (!promiseArray instanceof Array) { throw new Error("参数类型应为数组"); } let resolveResult = []; let flag = 0; let promiseCount = promiseArray.length;
for (let i in promiseArray) { Promise.resolve( promiseArray[i].then( (val) => { flag++; //resolveResult.push(val) //不使用push的原因-先完成的先push,后完成的后push,会导致返回的数组与promiseArray数组的顺序不同,因此不能使用push resolveResult[i] = val; if (flag == promiseCount) { return resolve(resolveResult); } }, (err) => { return reject(err); } ) ); } });}
let promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve(1); }, 3000);});
let promise2 = new Promise((resolve, reject) => { setTimeout(() => { resolve(2); }, 1000);});
let promise3 = new Promise((resolve, reject) => { setTimeout(() => { resolve(3); }, 2000);});
let promise4 = new Promise((resolve, reject) => { setTimeout(() => { resolve(4); }, 6000);});
let promise5 = new Promise((resolve, reject) => { setTimeout(() => { resolve(5); }, 4000);});
let promiseArray = [promise1, promise2, promise3, promise4, promise5];
AllPromise(promiseArray).then((res) => console.log(res));手写 Promise.allSettled
手写思路
- 接受一个参数,该参数是含有 promise 实例的数组
- 遍历传入的参数,用 promise.resolve 将参数转为 promise 对象
- 无论 promise 对象回调成功还是失败,都将返回结果存储到回调结果数组中。
- 设置 flag,每当一个 promise 对象回调成功或者失败,在 finally 阶段 flag+1,然后将回调结果添加到回调结果数组中,直到 flag 与传入的 promise 实例数组长度相同,则将带有所有回调结果的回调结果数组输出。
function allSettledPromise(promiseArray) { return new Promise((resolve, reject) => { if (!promiseArray instanceof Array) { throw new Error("参数类型应为数组"); } let resolveResult = []; let flag = 0; let promiseCount = promiseArray.length;
for (let i in promiseArray) { Promise.resolve( promiseArray[i] .then((val) => { resolveResult[i] = val; }) .catch((err) => { resolveResult[i] = err; }) .finally((res) => { flag++; if (flag == promiseCount) { return resolve(resolveResult); } }) ); } });}
let promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve(1); }, 3000);});
let promise2 = new Promise((resolve, reject) => { setTimeout(() => { resolve(2); }, 1000);});
let promise3 = new Promise((resolve, reject) => { setTimeout(() => { resolve(3); }, 2000);});
let promise4 = new Promise((resolve, reject) => { setTimeout(() => { reject("错误"); }, 6000);});
let promise5 = new Promise((resolve, reject) => { setTimeout(() => { resolve(5); }, 4000);});
let promiseArray = [promise1, promise2, promise3, promise4, promise5];
allSettledPromise(promiseArray).then((res) => console.log(res));// 返回结果-[ 1, 2, 3, '错误', 5 ]