回调地狱
1 2 3 4 5 6 7 8 9
| setTimeout(() => { console.log(1); setTimeout(() => { console.log(2); setTimeout(() => { console.log(3); }, 1000); }, 1000); }, 1000);
|
缺点:
-
代码耦合性太强, 牵一发而动全身, 难以维护
-
大量冗余代码相互嵌套, 代码可读性变差
如何解决回调地狱的问题
为了解决回调地狱的问题, ES6 新增了 Promise
的概念
Promise 的三种状态
Promise 通过自身状态, 来控制异步操作, Promise 实例有三种状态
-
异步操作未完成(pending
)
-
异步操作成功(fulfilled
)
-
异步操作失败(rejected
)
这三种的状态变化途径只有两种
一旦状态发生变化, 就会凝固, 不会发生新变化, Promise 的状态变化只会发生一次
-
异步操作成功,Promise 实例传回一个值(va1ue),状态变为 fulfilled
。
-
异步操作失败,Promise 实例抛出一个错误(error),状态变为 rejected
。
Promise 的链式调用
promise
支持链式调用, 通过 return
一个 promise实例
来链式调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const pro = new Promise((resolve, reject) => { setTimeout(() => { const res = Math.random() > 0.5; if (res) resolve(res); else reject(res); }, 1000); });
pro.then(res => { console.log(res); return 123; }) .then(res => { console.log(res); }) .catch(res => { console.log(res); });
|
注意:
-
如果返回的是非 Promise
对象, 那么默认转换为 fulfilled
,并将 return
的结果传递到下一个 then
-
如果返回 Promise
对象, 那么返回的对象的状态取决于 Promise
实例的状态
通过.catch 捕获错误
在 Promise 的链式操作中, 可以通过 .catch
来捕获错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| tfs.readFile("./txt/1.txt", "utf8") .then(result => { console.log(result); return tfs.readFile("./txt/12.txt", "utf8"); }) .then(result => { console.log(result); return tfs.readFile("./txt/3.txt", "utf8"); }) .then(result => { console.log(result); }) .catch(err => { console.log(err); });
|
Promise 的对象方法
Promise
是个对象, 也是一个构造函数
Promise.resolve()
返回一个 resolved
状态的 promise
实例
Promise.reject()
返回一个 reject
状态的 promise
实例
以上两个方法如果传入 promise
对象, 那么会原样返回, 不会更改实例状态
Promise.all()方法
Promise.all()
方法可以并行执行多个任务, 并且等到所有任务都 fulfilled
之后再执行 .then()
方法, 如果有一个 reject
, 那么状态就会变成 reject
注意:
-
只有 p1、p2、p3 的状态都变成 fulfilled
,p 的状态才会变成 fulfilled
,此时 p1、p2、p3 的返回值组成一个数组,传递给 p 的回调函数。
-
只要 pl、p2、p3 之中有一个被 rejected
,p 的状态就变成 rejected
,此时第一个被 reject
的实例的返回值,会传递给 p 的回调函数。
Promise.race() 方法
Promise.race()
方法会发起并行的 Promise
异步操作, 只要有一个任务 fulfilled
或 reject
, 整个 promise 就会 fulfilled
或 reject
Promise.allSettled()方法
该方法在全部的 promise
结束之后(成功或失败)执行 .then
, 返回一个包含状态和结果值得对象
1 2 3 4 5 6 7 8
| Promise.allSettled([pro(1), pro(-1), pro(1)]).then(res => { console.log(res); }); [ { status: "fulfilled", value: "123" }, { status: "rejected", reason: "err" }, { status: "fulfilled", value: "123" }, ];
|
Promise.any()
只要有一个任务变成 fulfilled
, 那么整个就会 fulfilled
, 只有全部 reject
, 那么才会 reject
,
与 race
的区别, race
只要第一个 reject
, 那么整体就会 reject
, 而 any
则是全部 reject
才会 reject
Promise 系列方法总结
方法名称 |
resolved 的条件 |
reject 的条件 |
resolve 返回值 |
all |
所有 Promise 实例都成功时 |
任意一个 Promise 实例失败时 |
包含所有实例结果数组 |
race |
第一个 Promise 实例状态变为 resolved 时 |
第一个 Promise 实例状态变为 rejected 时 |
第一个实例返回的结果 |
allSettled |
所有 Promise 实例完成,不论是否成功或失败 |
不会抛出异常 |
包含每个实例状态及结果的数组 |
any |
第一个 Promise 实例成功时 |
所有 Promise 实例均失败时 |
第一个实例的返回值 |
封装 Promise 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const pro = new Promise((resolve, reject) => { setTimeout(() => { const res = Math.random() > 0.5; if (res) resolve(res); else reject(res); }, 1000); });
pro.then(res => { console.log(res); return 123; }) .then(res => { console.log(res); }) .catch(res => { console.log(res); });
|
async/await
async/await
是 es8 (ECMA 2017)引入的新语法, 用来简化 Promise 的异步操作, 在 async/await
出现之前, 开发只能通过链式 .then
的方法处理 Promise 异步操作
基本使用
如果在一个 Promise
实例的前面加上 await, 那么可以直接获取到该实例的返回值
1 2 3 4 5 6 7 8 9
| async function getallFile() { const r1 = await getFile("./txt/1.txt"); console.log(r1); const r2 = await getFile("./txt/2.txt"); console.log(r2); const r3 = await getFile("./txt/3.txt"); console.log(r3); } getallFile();
|
async/await 的注意事项
-
如果 function
内部使用了 await
修饰, 那么 function
必须使用 async
修饰
-
在 async
方法中, 第一个 await
之前的代码会同步执行, await
之后的代码会异步执行
-
如果在 async
函数内返回一个普通值, async
函数会自动将该值包装成一个 Promise
对象,并直接 resolved
-
如果 async
函数内返回一个 promise
对象, async
函数返回的仍然是一个 Promise
对象,但其实际结果取决于所返回的 Promise
的状态
顶层 await
可以在 es 模块顶层中使用 await
1
| <script type="module">await xxx</script>
|
手写 Promise
Promise/A+标准
Promise/A+ 是 JavaScript 中 Promise 对象的一个规范,它是 Promises/A+ Working Group 提出的标准。这个组织成立于 2010 年,旨在推动 Promise 的发展并制定一致的行业规范。
Promise/A+ 主要定义了 Promise 对象的行为和特征,以确保不同实现之间具有良好的互操作性。其中包括:
-
Promise 是一个对象或函数,具有 then 方法。
-
then 方法接受两个参数:onFulfilled 和 onRejected,分别代表 Promise 成功时和失败时的回调函数。
-
then 方法返回一个新的 Promise 对象,并且可以链式调用多次,每次调用都会返回一个新的 Promise 对象。
-
最终状态只能是成功(fulfilled)或者失败(rejected),并且状态不可逆转。
-
如果某个 then 方法中的回调函数返回了一个值 x,则根据 x 的类型来处理该值:
- 如果 x 是一个 Promise 对象,则等待该 Promise 对象的状态变更,然后根据其最终状态处理当前 Promise 对象;
- 如果 x 是一个普通对象或函数,则将当前 Promise 对象的状态设为 fulfilled,并把 x 作为新的 Promise 对象传递给下一个 then 方法;
- 如果 x 失败,则将当前 Promise 对象的状态设为 rejected,并把 x 的错误信息向下传递给下一个 catch 方法。
通过符合 Promise/A+ 规范,就能确保不同的 Promise 实现都遵循相同的方法调用和状态转移逻辑,从而简化了 JavaScript 中异步编程的复杂性。
手写 Promise
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
| enum PromiseStatus { "pending", "fulfilled", "rejected", } export class MyPromise { status = PromiseStatus.pending; result: any = undefined; callback: { successCB: Function; failCB: Function; }[] = []; constructor(executor: (resolve: Function, reject: Function) => void) { try { executor(this.resolve.bind(this), this.reject.bind(this)); } catch (error) { this.reject(error); } } resolve(value: any) { if (this.status != PromiseStatus.pending) { return; } this.status = PromiseStatus.fulfilled; this.result = value; this.callback.forEach(item => { item.successCB(); }); } reject(value: any) { if (this.status != PromiseStatus.pending) { return; } this.status = PromiseStatus.rejected; this.result = value; this.callback.forEach(item => { item.failCB(); }); } then( successCB: ((resolveResult: any) => MyPromise | any) | undefined, failCB?: (rejectResult: any) => MyPromise | any, ) { if (!failCB) { failCB = err => err; } return new MyPromise((resolve, reject) => { if (this.status == PromiseStatus.fulfilled) { const cbResult = successCB && successCB(this.result); this.handleCBResult(cbResult, resolve, reject); } if (this.status == PromiseStatus.rejected) { const cbResult = failCB && failCB(this.result); this.handleCBResult(cbResult, resolve, reject); } if (this.status == PromiseStatus.pending) { this.callback.push({ successCB: () => { const cbResult = successCB && successCB(this.result); this.handleCBResult(cbResult, resolve, reject); }, failCB: () => { const cbResult = failCB && failCB(this.result); this.handleCBResult(cbResult, resolve, reject); }, }); } }); } catch(failCB: (rejectResult: any) => MyPromise | any) { return this.then(undefined, failCB); } handleCBResult( cbResult: MyPromise | any, resolve: Function, reject: Function, ) { if (cbResult instanceof MyPromise) { cbResult.then( res => { resolve(res); }, err => { reject(err); }, ); } else { if (this.status == PromiseStatus.rejected) { reject(cbResult); } else { resolve(cbResult); } } } static all(taskArr: MyPromise[]) { return new MyPromise((resolve, reject) => { const resultArr: any[] = []; taskArr.forEach(task => { task.then( res => { resultArr.push(res); if (resultArr.length === taskArr.length) { resolve(resultArr); } }, err => { reject(err); }, ); }); }); } static race(taskArr: MyPromise[]) { return new MyPromise((resolve, reject) => { for (const task of taskArr) { task.then( res => { resolve(res); }, err => { reject(err); }, ); } }); } static allSettled(taskArr: MyPromise[]) { return new MyPromise((resolve, reject) => { const resultArr: any[] = []; taskArr.forEach(task => { task.then( res => { resultArr.push({ status: "fulfilled", value: res, }); if (taskArr.length == resultArr.length) { resolve(resultArr); } }, err => { resultArr.push({ status: "rejected", value: err, }); if (taskArr.length == resultArr.length) { resolve(resultArr); } }, ); }); }); } static any(taskArr: MyPromise[]) { return new MyPromise((resolve, reject) => { let errCount = 0; taskArr.forEach(task => { task.then( res => { resolve(res); }, err => { errCount++; if (taskArr.length == errCount) { reject(err); } }, ); }); }); } }
|