解决
1) 解决并发问题 (同步的多个异步方法的执行结果 promise.all)
2) 解决链式调用问题 (先获取name,再获取age,再获取…) 回调地狱 解决多个回调嵌套的问题(不是完全解决,.then.then也是回调)
分析
Promise 是一个类
1) 每次new一个 Promise 都需要传递一个执行器,执行器(executor)是立即执行的
1
2
3
4
5
6new Promise( ()=>{
console.log("init");
})
console.log('1');
// 这里先执行的是 init 再执行 1
// 结果:init 1
2) 执行器函数中有2个参数 resolve、 reject。
3) 默认Primose有3个状态:pedding、fulfilled、rejected。 pedding => resolve 表示成功 pedding=> reject 表示拒绝。
4) 如果一旦成功不能再失败, 一旦失败不能成功了 只有当状态为pending的时候才能改变。
5) 每个Promise都有一个then方法。
手写一个Promise
首先建2个文件 1.promise.js
(用于执行一系列操作) promise.js
(用于放置手写的Promise)
基础封装
【1】
1 | // 1.promise.js |
1 | // promise.js |
【1】中,1.promise.js
依次取消注释执行line4、5
【2】
当执行1.promise.js
中line6时
1 | throw new Error("失败"); |
需要做异常处理:
1 | // promise.js |
这时候执行1.promise.js
line6 就能在line11捕获输出,执行结果: 失败的回调 Error: 失败
【3】
当同时打开1.promise.js
line4、6或line5、6时,执行结果都是 失败的回调 Error: 失败,这里不符合分析的第4点,
只有当状态为pending时才能改变状态
1 | // promise.js |
【4】
查看文档 https://promisesaplus.com/ promises/A+2.2.6
then may be called multiple times on the same promise 相同的promise被多次调用then
1 | // 1.promise.js |
【5】
当1.promise.js
改成如下:
1 | // 1.promise.js |
自己写的和原生的结果不同,原生的会等到异步执行结束再执行then,而自己写的直接执行的then导致没有内容输出,
当前执行异步时状态是pending 而我们这里then函数中PENDING状态还未写,如下修改:
1 | // promise.js |
这就是典型的发布订阅 当1.promise.js
line4-7执行状态为pending时,p.then()里面的方法先存起来,等到定时器里面resolve的时候依次执行。
概括 订阅:弄个数组 存起来 ; 发布:将存起来的数组里面的内容执行一遍。
所以现在相当于1.promise.js
line9-29在订阅 line6在发布
这就是同步的时候直接触发,异步的时候发布订阅
链式调用(应用)
现在的需求:读取name中的值 再读取age中的值, 以前的做法如下:
1
2
3
4
5
6
7
8
9
10
11let fs = require('fs');
fs.readFile(__dirname + '/name.txt', 'utf8', (err, data)=>{
if(err){
console.log(err);
}
fs.readFile(__dirname + '/age.txt', 'utf8', (err, data)=>{
if(err){
console.log(err);
}
})
})
现在用promise做法如下:
1 | let fs = require('fs'); |
成功之后不通过回调,而是通过then
readFile(__dirname + ‘/name.txt’, ‘utf8’) 返回的是Promise实例,记住 能then的就是Promise实例
如果需要改造成Promise 就先将回调的方法 改成Promise
完整代码如下:
【1】then方法中返回: 1) 普通值
1 | let fs = require('fs'); |
step1: line12 then里面返回的line14 它会把值传到then的外层到line18 可以看到打印结果: successline18: false
step2: 第一步走的成功 希望能走失败,现加入了line19 或line20-22 结果: line29
step3: 可以看到经历了第二步的失败 第三部从line29打印出来 失败也可以变成成功 结果:line32
1 | // 打印结果: |
【2】then方法中返回: 2) 返回的是一个promise
1 | let fs = require('fs'); |
所以可以用这种方式来解决回调嵌套的问题 并且很容易捕获到错误
并且当line10读取报错(假设读取的是‘/name11.txt’),首先找then自身的错误处理,这里line14没有错误处理就找下一个line18去输出, 同样的line13读取报错(假设读取的是‘/age.txt123’)也可以在line18获取
1 | // 如果这里line4-6添加了err |
总结:
链式调用 如果返回一个普通值 会走下一个then的成功
抛出错误 走then失败的方法
如果是promise 就让promise执行 采用它的状态是返回了一个新的promise 来实现链式调用
继续封装 链式调用
【6】
当一个promise的then方法中返回了一个普通值, 会将这个普通纸传到外层then的回调里 如下面的line6
这里p这个promise调用p.then之后返回的是一个新的promsie
1 | // 1.promsie.js |
1 | // 这里p这个promise调用p.then之后返回的是一个新的promsie |
所以这里面promise.js
改为:
1 | // promise.js |
现在将1.promise.js
中line6注释,line7打开
promise.js
代码中需要捕获错误,如下:
1 | class Promise{ |
【7】
当一个promise的then中返回一个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// 1.promise.js
const Promise = require('./promise');
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hello');
});
})
p.then(data=>{
return new Promise( (resolve, reject) => {
setTimeout(() => {
resolve("word")
});
})
}).then(data=>{
console.log("成功:",data)
},err=>{
console.log('err:', err);
})
// 调用当前的promise.js
// 执行结果:
/*
成功: Promise {
value: undefined,
reason: undefined,
status: 'PENDING',
onResolvedCallbacks: [],
onRejectedCallbacks: [] }
*/
会把状态为PENDING状态的结果返回到line14的data
1
2
3
4
5
6 new Promise( (resolve, reject) => {
setTimeout(() => {
resolve("word")
});
})
// 会直接把这个值传递给 data 也就是x 是个等待状态
我们希望的是能把返回值里面的promise执行完之后再传到then
所以现在需要判断x是否为promise
这里可以看 promises/A+
里面2.2.7 resolvePromise , promise的处理函数
1 | // promise.js |
1 | const Promise = require('./promise'); |
【8】
现在来处理resolvePromise函数
1 | // 1.promise.js |
1 | // promise.js |
【9】
1 | /***************针对 promise.js line 12 2.3.3.2 取then的过程中抛出异常 */ |
1 | // promise.js |
小结: 判断是否为promise
1 | 1、if( (typeof x === 'object' && x !== null) || (typeof x === 'function')) |
小知识点: then.call(x) 指 让then的this指向x 等价于 x.then
【10】
1 | // 1.promise.js |
当如1.promise.js
line 6-16之间嵌套了多个promise 我们希望的是能够执行到最里面的resolve或者reject结束之后再执行下一个then方法,即有可能上面resolvePromise
函数里line18的y是个promise ,所以下面需要对y的类型做判断
1 | // promise.js |
参考promsies/A+ 2.3.3.3 If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.
如果1.promise.js
line7-15返回的是别人封装的Promise,假设它里面并没有做调取成功了之后不能再掉失败,调取了失败之后不能再调成功的判断,所以上面新增了一个 called 用来防止多次调用。
【11】
这里对应promises/A+ 2.2.1里面的几点
1 | // 1.promise.js |
有一种穿透的感觉 这个then里没有写,就到下一个then,代码如下line34-35直接把值往下传
1 | // promise.js |
情况4:resolve里面返回了一个promise 可以分别用原生的和自己写的做对比
1 | // 1.promise.js |
1 | // promise.js |
情况5:
如下情况 catch line10-12 和 line13-15(这2种情况)可用原生方法试一下 效果是一样的
还可以在line12之间用.then() 空的then方法
1 | // 1.promise.js |
当cathch后面跟了一个then 还是会走then里面的成功方法
1 | // 1.promise.js |
小结:line11-13和line14-16是一样的,相当于catch就是then里面少写了一个成功的方法 是then的语法糖 对应的promise.js
1 | // promsie.js |
【12】
1 | let p = new Promise( (resolve, reject) => { |
上面 line1-5 和 line6-8是一样的 resolve是当前类上的静态方法 说白了也是一个语法糖
1 | // promise.js |
完整版Promise.js
1 | const resolvePromise = (promise2, x, resolve, reject) => { |
拓展
1)
1 | let p = new Promise((resolve, reject) => { |
2) promise 中的链式调用如何中断 ?
1 | let p = new Promise((resolve, reject) => { |
现在希望在line5这个then就停止 如下:
1 | let p = new Promise((resolve, reject) => { |
总结: 中断promise链 就是返回一个等待的promise
3)finally实现原理
1 | Promise.resolve().finally(()=>{ |
Promise.reject().finally 因为是这样调用的finally 所以这个finally是实例上的方法不是类上的方法 类上的方法直接Promise.调用
下面自己手下写finally
1 | Promise.prototype.finally = function(callback){ // 疑问 为什么实例是Promise.prototype |
改进:
1 | Promise.prototype.finally = function(callback){ |
测试
1 | // 4个小示例 |
用自己写的finally试验
1 | Promise.prototype.finally = function(callback){ |
总结:
finall里面成功就不管 按照上一个then直接的结果返回 如果是失败则走这个失败
分析:
① Promise.resolve()
时(成功),如果这里line4 resolve(callback())
成功, 就直接走后面接的 .then(()=>val);
这里的val是上一个then传过来的值,所以成功时候不改变最终结果 ;如果line4 resolve(callback())
失败,就走 .then(()=>val)
的失败,因为这里的.then里面没有失败的回调,所以会接着找下面的回调 即line18-22也就是之前的 .catch
,会把 resolve(callback())
的失败结果输出, 可以在line4 .then(()=>val, (err)=>{console.log(err)})
打印 resolve(callback())
失败的结果,那么line18-22,就会走line19 打印 success: undefined 。
② Promise.reject()
时(失败),如果这里line7 resolve(callback())
成功,就直接走后面接的 then(()=>{throw err})
,将上一个then的错误传递下去,走line21; 如果line7 resolve(callback())
失败,就走 .then(()=>{throw err})
这里没有失败的接受,就走最外面line21 。
4)(待看视频理解 找不到了…)
1 | Promise.resolve().then(console.log('now')); |
相当于是then的成功回调,then里new 的promise2的executor立即执行,当前状态已经 FULFILLED,然后setTimeout执行的promise2的resolve
5)Promise.race 赛跑 哪个快用哪个
1 | let p1 = new Promise( (resolve, reject)=>{ |
示例
1 | // Promsie.race |
手写一个race
1 | Promise.race = function(promises){ |
应用场景 :一个网站有个接口 在两个服务器上 谁快用谁
6)题目: 如何放弃某个promise的执行结果?
1 | let p = new Promise( (resolve, reject)=>{ |
如下:
1 | function wrap(p1){ |
7) Promise.try 这个方法现在不支持
1 | // 既能捕获同步 也能捕获异步 |
8) Promise.all 全部
处理多个异步并发的问题
1 | let fs = require('fs').promises; |
全部完成才算完成 如果有一个失败就失败 Promsie.all是按照顺序执行的
1 | let fs = require('fs').promises; |
手写Promise.all
1 | const isPromise = value =>{ |
ES6中的Promise
catch
1 | new Promise((resolve, reject) => { |
上面代码中,调用resolve(1)以后,后面的console.log(2)还是会执行,并且会首先打印出来。这是因为立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。
一般来说,调用resolve或reject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。
1 | new Promise((resolve, reject) => { |
跟传统的try/catch代码块不同的是,如果没有使用catch()方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。
内部有语法错误,浏览器运行到这一行,会打印出错误提示,但是不会退出进程、终止脚本执行。
Promise.all
注意,如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法。
1 | const p1 = new Promise((resolve, reject) => { |
上面代码中,p1会resolved,p2首先会rejected,但是p2有自己的catch方法,该方法返回的是一个新的 Promise 实例,p2指向的实际上是这个实例。该实例执行完catch方法后,也会变成resolved,导致Promise.all()方法参数里面的两个实例都会resolved,因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。
如果p2没有自己的catch方法,就会调用Promise.all()的catch方法。