1. promise 入门简介 Promise 是用来解决异步编程的问题。
1.1 JS 中分同步Api 和 异步Api。 同步API:只有当前API执行完成后,才能继续执行下一个API
1 2 3 4 for (let i = 0 ; i < 10000 ; i++) { console .log (i); } console .log ('同步代码执行' );
只有上面一万行数值打印完,才会打印’同步代码执行’
异步API:当前API的执行不会阻塞后续代码的执行
1 2 3 4 5 console .log ('before' );setTimeout ( () => { console .log ('last' ); }, 2000 ); console .log ('after' );
setTimeout定时器要在2s秒后才执行,js引擎不会卡在定时器这,会先执行同步代码,等同步代码执行完再执行异步代码定时器(在这只需要先记住定时器是异步代码)
1.2 同步API, 异步API的区别( 获取返回值 ) 同步API可以从返回值中拿到API执行的结果, 但是异步API是不可以的
同步:
1 2 3 4 5 function sum (n1, n2) { return n1 + n2; } const result = sum (10 , 20 );
异步:
1 2 3 4 5 6 function getMsg () { setTimeout (function ( ) { return { msg : 'Hello Node.js' } }, 2000 ); } const msg = getMsg ();
所以异步函数没法用返回值获取值
1.3 回调函数 自己定义函数让别人去调用。
使用回调函数可以获取异步API执行结果
1 2 3 4 5 6 7 8 9 10 function getMsg (callback) { setTimeout (() => { let a = '异步函数结果' callback (a) }, 2000 ) } getMsg ((result ) => { console .log (result); })
1.4代码执行顺序分析 1 2 3 4 5 6 7 8 console .log ('代码开始执行' );setTimeout (() => { console .log ('2秒后执行的代码' ); }, 2000 ); setTimeout (() => { console .log ('0秒后执行的代码' ); }, 0 ) console .log ('代码结束执行' )
异步代码执行区的异步函数执行完成,将要执行专属的回调函数时,就会将回调函数放入回调函数队列,等同步代码执行区的代码执行完成后,就把回调函数队列的回调函数加入同步代码执行区。
1.5 JS 常见异步编程
fs 文件操作
数据库操作
AJAX 网络请求
定时器 (setTimeout)
1.6 Promise 出现的需求 我们先来个场景:
现在需要用 fs 文件操作 读取文件,但读取文件的顺序必须是先读A,再读B,再读C。
因为 fs 文件操作是异步的,没办法写成同步代码那样,按顺序如下
假设 fs 文件操作 是 同步的
1 2 3 4 5 const fs = require ('fs' )fs.readFile ('a.txt' ); fs.readFile ('b.txt' ); fs.readFile ('c.txt' );
但 fs文件操作 是异步编程, 要按照顺序读取的话就不能写成同步代码的形式,只能这样写:
1 2 3 4 5 6 7 8 9 10 11 const fs = require ('fs' )fs.readFile ('a.txt' , (err, data ) => { console .log ('第一个执行' , data); fs.readFile ('b.txt' , (err, data ) => { console .log ('第二个执行' , data); fs.readFile ('c.txt' , (err, data ) => { console .log (data); }) }) })
连续嵌套着的回调函数可读性非常差,也称为回调地狱
Promise出现的目的是解决Node.js异步编程中回调地狱的问题。
先来一个Promise 案例,有个感性的认识
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 let promise = new Promise ((resolve, reject ) => { setTimeout (() => { if (true ) { resolve ({name : 'aaaaa' }) } else { reject ('失败了' ) } }, 2000 ); }); promise.then (result => { console .log (result); }).catch ( err => { console .log (err); })
先展示 promise 解决回调地狱的问题,后面会详细介绍promise。
Promise解决 按顺序读取A文件,B文件,C文件 的回调地狱问题 :
先有个感性的认识
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 const fs = require ('fs' )let p1 = new Promise ((resolve, reject ) => { fs.readFile ('a.txt' , 'utf-8' , (err, data ) => { resolve (data) }) }) let p2 = new Promise ((resolve, reject ) => { fs.readFile ('b.txt' , 'utf-8' , (err, data ) => { resolve (data) }) }) let p3 = new Promise ((resolve, reject ) => { fs.readFile ('c.txt' , 'utf-8' , (err, data ) => { resolve (data) }) }) p1.then (r1 => { console .log (r1); return p2; }).then (r2 => { console .log (r2); return p3; }).then (r3 => { console .log (r3); })
2. Promise 常见常见练习,对Promise 有更好的熟悉感 2.1 promise 实践练习-fs读取文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const fs = require ('fs' );fs.readFile ('./test.txt' , (err, data ) => { if (err) throw err; console .log (data.toString ()); }) let promise = new Promise ((resolve, reject ) => { fs.readFile ('./test.txt' , (err, data ) => { if (err) reject (err); resolve (data.toString ()); }) }) promise.then (result => { console .log (result); })
2.2 promise 实践练习-AJAX请求 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 const btn = document .querySelector ('#btn' );btn.addEventListener ("click" , () => { const xhr = new XMLHttpRequest (); xhr.open ('GET' , 'https://api.apiopen.top/getJoke' ); xhr.send (); xhr.onreadystatechange = function ( ) { if (xhr.readyState === 4 ) { if (xhr.status >= 200 && xhr.status < 300 ) { console .log (xhr.response ); } else { console .log (xhr.status ); } } } }) btn.addEventListener ("click" , () => { const promise = new Promise ((resolve, reject ) => { const xhr = new XMLHttpRequest (); xhr.open ('GET' , 'https://api.apiopen.top/getJoke' ); xhr.send (); xhr.onreadystatechange = function ( ) { if (xhr.readyState === 4 ) { if (xhr.status >= 200 && xhr.status < 300 ) { resolve (xhr.response ); } else { reject (xhr.status ); } } } }) promise.then (result => { console .log (result); }).catch (err => { console .log (err); }) })
2.3 util.promisify 方法进行 promise 风格转化
这个是用在node.js 的环境下,我在用node.js 写后端时用到过这种方法
1 2 3 4 5 6 7 8 9 10 11 12 const util = require ('util' );const fs = require ('fs' );let mineReadFile = util.promisify (fs.readFile );mineReadFile ('./test.txt' ).then (result => { console .log (result); }).catch (err => { console .log (err); })
2.4 promise 封装练习-AJAX请求 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 function sendAJAX (url ) { return new Promise ((resolve, reject ) => { const xhr = new XMLHttpRequest (); xhr.open ('GET' , url); xhr.send (); xhr.onreadystatechange = function ( ) { if (xhr.readyState === 4 ) { if (xhr.status >= 200 && xhr.status < 300 ) { resolve (xhr.response ); } else { reject (xhr.status ) } } } }) } sendAJAX ('https://api.apiopen.top/getJoke' ).then (result => { console .log (result); }).catch (err => { console .log (err); })
3. promise 的详细介绍 在了解 promise 的基本流程前,先要知道 promise的一些基本属性
3.1 promise 的状态 promise 的状态时 promise实例对象中的一个属性 [PromiseState]
pending 进行中
resolved / fulfilled 成功
rejected 失败
状态只能由 Pending
变为 Fulfilled
或由 Pending
变为 Rejected
,且状态改变之后不会在发生变化,会一直保持这个状态。
3.2. promise 对象的值 实例对象中的另一个属性 [PromiseResult]
保存着异步任务 [成功/失败] 的结果
3.3 promise 的基本流程
3.4 promise Api 的详细介绍 3.4.1 Promise 构造函数:Promise(executor) (1) executor 函数:执行器 (resolve, reject) => {}
(2) resolve 函数:内部定义成功时我们调用的函数 value => {}
(3) reject 函数:内部定义失败时我们调用的函数 reason => {}
说明:executor 会在 Promise 内部立即同步调用,异步操作在执行器中执行
代码
1 2 3 4 5 6 7 <script> let p = new Promise ((resolve, reject ) => { console .log (111 ); }); console .log (222 ); </script>
3.4.2 Promise.prototype.then 方法 (onResolved, onRejected) => {} (1) onResolved 函数:成功的回调函数 (value) => {}
(2) onRejected 函数:失败的回调函数 (reason) => {}
说明:指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调返回一个新的 promise 对象
1 2 3 4 5 promise.then (value => { console .log (value); }, reason => { console .log (reason); })
3.4.3 Promise.catch 方法:(reason) => {} (1) reason:失败的数据或Promise对象
说明:返回一个 失败的 promise 对象
1 2 3 sendAJAX ('https://api.apiopen.top/getJoke' ).catch (reason => { console .log (reason); })
3.4.4 Promise.resolve 方法:(value) => {} (1) value:成功的数据或 promise 对象
说明:返回一个成功/失败的 promise 对象
1 2 3 4 5 6 7 let p1 = Promise .resolve (521 );let p2 = Promise .resolve (new Promise ((resolve, reject ) => { resolve ('ok' ); })) console .log (p2);
3.4.5 Promise.reject 方法:(reason) => {} (1) reason:失败的原因
说明:返回一个失败的 promise 对象
1 2 3 4 let p1 = Promise .reject (521 )let p2 = Promise .reject (new Promise ((resolve, reject ) => { resolve ('ok' ) }))
3.4.6 Promise.all 方法:(promises) => {} (1) promises:包含 n 个 promise 的数组
说明:返回一个新的 promise,只有所有的 promise 都成功才成功,只要有一个失败了就直接失败
不演示了,写的太累了,看字面意思就知道这个函数的作用了。
3.4.7 Promise.race 方法:(promises) => {} (1) promises:包含 n 个 promise 的数组
说明:返回一个新的 promise,第一个完成的 promise 的结果状态就是最终的结果状态
4. promise 的几个关键问题 一:如何改变 promise 的状态
1 2 3 4 5 6 7 8 let p = new Promise ((resolve, reject ) => { resolve ('ok' ) reject ('err' ) throw '出问题了' ; })
二:一个 promise 指定多个成功/失败回调函数,都会调用吗?
当 promise 改变为对应状态时都会调用
1 2 3 4 5 6 7 8 9 10 11 12 let promise = new Promise ((resolve, reject ) => { resolve ('Ok' ); }) promise.then (res => { console .log (res); }) promise.then (res => { alert (res); })
三:改变 promise 状态 和 指定回调函数谁先谁后?
(1):都有可能,正常情况下是先指定回调再改变状态,但也可以先改变状态再指定回调
正常情况
promise 执行器内部是 异步操作,所以是先指定回调,再改变状态
1 2 3 4 5 6 7 8 9 10 11 12 let promise = new Promise ((resolve, reject ) => { setTimeout (() => { resolve ('Ok' ) }, 1000 ) }) promise.then (res => { console .log (res); })
(2):如何先改状态再指定回调?
在执行器中直接调用 resolve() / reject()
延迟更长时间才调用 then()
1 2 3 4 5 6 7 8 9 let promise = new Promise ((resolve, reject ) => { resolve ('Ok' ); }) promise.then (res => { console .log (res); })
四:promise.then() 返回的新 promise 的结果状态由什么决定
(1) 简单表达:由 then() 指定的回调函数执行
(2) 详细表达:
如果抛出异常,新 promise 变为 rejected,reason 为 抛出的异常
如果返回的是非 promise 的任意值,新 promise 变为 resolved, value为返回的值
如果返回的是另一个新 promise,此 promise 的结果就会成为 新 promise 的结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let promise = new Promise ((resolve, reject ) => { resolve ('Ok' ); }) promise.then (res => { console .log (res); return 123 ; return new Promise ((resolve, reject ) => { resolve ('DDD' ); }) })
五:promise 如何串连多个操作任务?
(1) promise 的 then() 内部返回一个新的 promise,可以 .then() 进行链式调用
(2) 通过 then 的链式调用串连 多个同步/异步任务
就最开始提出按顺序读取a.txt , b.txt, c.txt 终于可以解释了。
如何用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 const fs = require ('fs' )let p1 = new Promise ((resolve, reject ) => { fs.readFile ('a.txt' , 'utf-8' , (err, data ) => { resolve (data) }) }) let p2 = new Promise ((resolve, reject ) => { fs.readFile ('b.txt' , 'utf-8' , (err, data ) => { resolve (data) }) }) let p3 = new Promise ((resolve, reject ) => { fs.readFile ('c.txt' , 'utf-8' , (err, data ) => { resolve (data) }) }) p1.then (r1 => { console .log (r1); return p2; }).then (r2 => { console .log (r2); return p3; }).then (r3 => { console .log (r3); })
六:promise 异常穿透
(1) 当使用 promise 的 then 链式调用时,可以在最后指定失败的回调
(2) 前面任何操作出了异常,都会传到最后失败的回调中处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 let promise = new Promise ((resolve, reject ) => { reject ('Err' ); }) let p = promise.then (res => { throw '失败了' ; }).then (value => { console .log (222 ); }).then (value => { console .log (333 ); }).catch (reason => { console .log (reason); })
七:中断 promise 链?
(1) 当使用 promise 的 then 链式调用时,在中间中断,不再调用后面的回调函数
(2) 办法:在回调函数中返回一个 pendding 状态的 promise 对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let promise = new Promise ((resolve, reject ) => { resolve ('Ok' ); }) let p = promise.then (res => { console .log (111 ); return new Promise (() => {}); }).then (value => { console .log (222 ); }).then (value => { console .log (333 ); }).catch (reason => { console .log (reason); })
5. 手写 Promise 5.1 定义整体结构 创建两个文件 index.html,promise.js
在 promise.js 写最基本的 promise 结构
1 2 3 4 5 6 7 8 function Promise (executor ) {} Promise .prototype .then = function (onResolved, onRejected ) { }
index.html 里引入 我们刚写的 promise.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta http-equiv ="X-UA-Compatible" content ="IE=edge" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > <script src ="./promise.js" > </script > </head > <body > <script > let p = new Promise ((resolve, reject ) => { resolve ('Ok' ); }) p.then (res => { console .log (res); }, reason => { console .log (reason); }) </script > </body > </html >
5.2 封装 resolve 和 reject 结构 promise.js 里的 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function Promise (executor ) { function resolve (data ) { } function reject (data ) { } executor (resolve, reject); }
5.3 resolve 和 reject 代码的实现 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 function Promise (executor ) { this .PromiseState = 'pending' ; this .PromiseResult = null ; const that = this ; function resolve (data ) { that.PromiseState = 'fulfilled' ; that.PromiseResult = data; } function reject (data ) { that.PromiseState = 'rejected' ; that.PromiseResult = data; } executor (resolve, reject); } Promise .prototype .then = function (onResolved, onRejected ) {}
5.4 throw 抛出异常改变状态 1 2 3 4 5 6 7 try { executor (resolve, reject); } catch (error) { reject (error) }
5.5 promise 对象状态只能修改一次 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function resolve (data ) { if (that.PromiseState !== 'pending' ) return ; that.PromiseState = 'fulfilled' ; that.PromiseResult = data; } function reject (data ) { if (that.PromiseState !== 'pending' ) return ; that.PromiseState = 'rejected' ; that.PromiseResult = data; }
5.6 then方法执行回调 1 2 3 4 5 6 7 8 9 10 Promise .prototype .then = function (onResolved, onRejected ) { if (this .PromiseState === 'fulfilled' ) { onResolved (this .PromiseResult ); } if (this .PromiseState === 'rejected' ) { onRejected (this .PromiseResult ); } }
5.7 异步任务回调的执行
这部分我感觉就是promise 最核心关键的地方了。
在前面的执行器函数中,一直是同步执行的,所以在 then 方法中能直接获取到 PromiseState 和 PromiseResult的值。但现在要在 executor 执行器中进行异步函数调用了,而 then 方法中回调函数就不能直接获得 PromiseState 和 PromiseResult。要等异步函数执行结束后才能得到。而如何处理才能使得 then 方法 得到 executor 执行器中 异步函数产生的值?
官方的解决方法是:
在 then 方法中判断 promise 的状态,如果是 pending,说明异步函数还没执行结束,这时不能直接调用 then 方法中的回调函数,先把回调函数保存下来。如何保存?在 promise 构造函数中用一个属性保存。
1 2 3 4 function Promise (executor ) { this .callback = {} }
然后在 pending 状态时,保存回调函数
其实这时候 then 方法已经结束了,没有把回调函数进行调用,所以就先把回调函数存到 p 这个对象的 callback 上。在异步任务结束后,交给window 托管的 resolve 开始执行,这个 window 托管的函数使用了 p 对象中存着的 callback 函数。
index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <script > let p = new Promise ((resolve, reject ) => { setTimeout (() => { reject ('err' ) }, 1000 ) }) p.then (res => { console .log (res); }, reason => { console .log (reason); }) console .log (p); </script >
promise.js
1 2 3 4 5 6 7 8 9 10 11 Promise .prototype .then = function (onResolved, onRejected ) { if (this .PromiseState === 'pending' ) { this .callback = { onResolved : onResolved, onRejected : onRejected } } }
在 异步任务完成后,调用回调函数
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 function resolve (data ) { if (that.PromiseState !== 'pending' ) return ; that.PromiseState = 'fulfilled' ; that.PromiseResult = data; if (that.callback .onResolved ) { that.callback .onResolved (data); } } function reject (data ) { if (that.PromiseState !== 'pending' ) return ; that.PromiseState = 'rejected' ; that.PromiseResult = data; if (that.callback .onRejected ) { that.callback .onRejected (data); } }
5.8 指定多个回调的实现 问题:
我们希望这两个回调都能执行,但保存第二个回调时会将第一个回调覆盖掉,所以原来的保存方法不行,需要修改。
修改前
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 let p = new Promise ((resolve, reject ) => { setTimeout (() => { reject ('err' ) }, 1000 ) }) p.then (res => { console .log (res); }, reason => { console .log (reason); }) p.then (res => { alert (res); }, reason => { alert (reason); })
修改后
改用数组存 callback,再遍历把每个 回调函数都调用。
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 this .callback = []function resolve (data ) { if (that.PromiseState !== 'pending' ) return ; that.PromiseState = 'fulfilled' ; that.PromiseResult = data; that.callback .forEach (item => { item.onResolved (data); }); } function reject (data ) { if (that.PromiseState !== 'pending' ) return ; that.PromiseState = 'rejected' ; that.PromiseResult = data; that.callback .forEach (item => { item.onRejected (data); }); } Promise .prototype .then = function (onResolved, onRejected ) { if (this .PromiseState === 'fulfilled' ) { onResolved (this .PromiseResult ); } if (this .PromiseState === 'rejected' ) { onRejected (this .PromiseResult ); } if (this .PromiseState === 'pending' ) { this .callback .push ({ onResolved : onResolved, onRejected : onRejected }); } }
5.9 同步修改状态 then 方法结果返回
这一块部分相当复杂,需要慢慢来理一下,后面异步的部分会更发杂(麻了)
前面的知识前提:
我们在前面的 promise 中学到 p.then() 这个方法会返回一个 promise 对象, 返回的具体内容根 回调函数的 return 内容相关。
现在这一部分是先按同步的来,下一章内容是异步
先写整体骨架代码:
1 2 3 4 5 6 7 8 9 10 11 12 let p = new Promise ((resolve, reject ) => { resolve ('Ok' ) }) let outerResult = p.then (value => { return 521 ; }, reason => { console .log (reason); }) console .log (outerResult);
我们先返回 非 promise 的值,返回 521。
在 promise.js 中 then() 方法 返回 一个 promise 对象
这是回调函数返回是 非 promise 的情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Promise .prototype .then = function (onResolved, onRejected ) { return new Promise ((resolve, reject ) => { if (this .PromiseState === 'fulfilled' ) { let result = onResolved (this .PromiseResult ); if (result instanceof Promise ) { } else { resolve (result); } } }) }
结果
tips:
我把 p.then() 返回 的 promise 对象命名为 outerResult,在 p.then() 内的回调函数返回的 promise 对象命名为 result
这是回调函数返回是 promise 的情况,并且这个 promise 是成功的状态
1 2 3 4 5 6 7 8 let outerResult = p.then (value => { let result = new Promise ((resolve, reject ) => { resolve ('Hello' ); }) return result; }, reason => { console .log (reason); })
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Promise .prototype .then = function (onResolved, onRejected ) { return new Promise ((resolve, reject ) => { if (this .PromiseState === 'fulfilled' ) { let result = onResolved (this .PromiseResult ); if (result instanceof Promise ) { result.then (value => { resolve (value); }, reason => { reject (reason) }) } else { resolve (result); } } }) }
完整代码
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 Promise .prototype .then = function (onResolved, onRejected ) { return new Promise ((resolve, reject ) => { if (this .PromiseState === 'fulfilled' ) { try { let result = onResolved (this .PromiseResult ); if (result instanceof Promise ) { result.then (value => { resolve (value); }, reason => { reject (reason) }) } else { resolve (result); } } catch (error) { reject (error); } } if (this .PromiseState === 'rejected' ) { onRejected (this .PromiseResult ); } if (this .PromiseState === 'pending' ) { this .callback .push ({ onResolved : onResolved, onRejected : onRejected }); } }) }
5.10 异步修改状态 then 方法返回
这一部分就更复杂了。。。
因为 executor 内部是异步函数执行,所以在执行到 p.then() 时,p 的状态是 pending ,直接进入 .then() 方法的最后一个判断。说的太抽象了,来看下实际代码。
index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let p = new Promise ((resolve, reject ) => { setTimeout (() => { resolve ('Err' ); }, 1000 ) }) let outerResult = p.then (value => { console .log (value); }, reason => { console .log (reason); }) console .log (outerResult);
promise.js
之前写的代码,在碰到 executor 里是异步函数时,操作是先把回调函数保存起来,等异步函数执行结束,调用 resolve 或 reject 时,再调用 回调函数。
现在因为 .then() 方法执行后要返回一个 promise 对象,如果直接简单的保存回调函数,返回的 promise 对象的状态一直是 pending,PromiseResult 这个属性也一直是 null。
所以要对保存回调函数进行魔改,让后面执行这个回调函数时,能修改 outerResult 的两个属性。
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 Promise .prototype .then = function (onResolved, onRejected ) { return new Promise ((resolve, reject ) => { if (this .PromiseState === 'pending' ) { this .callback .push ({ onResolved : function (data ) { try { let res = onResolved (data); if (res instanceof Promise ) { res.then (value => { resolve (value); }, reason => { reject (reason); }) } else { resolve (res); } } catch (error) { reject (error) } }, onRejected : function (data ) { try { let res = onRejected (data); if (res instanceof Promise ) { res.then (value => { resolve (value); }, reason => { reject (reason); }) } else { reject (res); } } catch (error) { reject (error) } } }); } }) }
5.11 catch方法-异常穿透与值传递 catch 方法是获取失败的值,因为前面 then() 方法 已经写的很完善了,所以 catch 只要调用一下 then() 就好
index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 let p = new Promise ((resolve, reject ) => { setTimeout (() => { reject ('Err' ); }, 1000 ) }) let res = p.catch (reason => { console .log (reason); return 321 ; }) console .log (res);
promise.js
1 2 3 Promise .prototype .catch = function (onRejected ) { return this .then (undefined , onRejected); }
接下来就是要完成 catch 方法的异常穿透效果
我按照顺序写下来:
index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 let p = new Promise ((resolve, reject ) => { setTimeout (() => { reject ('Err' ); }, 1000 ) }) let res = p.then (value => { console .log (111 ); }).then (value => { console .log (222 ); }).then (value => { console .log (333 ); }).catch (reason => { console .log (reason); })
这时候会出现这个问题:
说 onRejected 方法不存在,这是什么情况?
解释:
executor 执行器在执行它内部异步代码前,同步代码已经执行结束了,也就是 p.then() 执行完毕,把 then 内的回调方法存到了 p 的自身属性上。而 then 的回调参数有两个:onResolved,onRejected。在这个案例中只传了一个 onResolve,没有onRejected,所以 保存在 p 本身上的回调函数 onRejected 就为空。
所以我们要主动给 then 方法内加上一个 onRejected 回调函数。
1 2 3 4 5 6 7 8 9 10 11 12 Promise .prototype .then = function (onResolved, onRejected ) { if (typeof onRejected !== 'function' ) { onRejected = reason => { throw reason; } } }
这时候在最后的 catch 就能接受到这个异常了
值传递 的话就是 then 方法连 onResolved 回调函数都不传递了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 let p = new Promise ((resolve, reject ) => { setTimeout (() => { reject ('Err' ); }, 1000 ) }) let res = p.then ().then (value => { console .log (222 ); }).then (value => { console .log (333 ); }).catch (reason => { console .log (reason); })
这时候处理方法和上面的类似,加一个 onResolved 回调就行
1 2 3 4 5 6 7 if (typeof onResolved !== 'function' ) { onResolved = value => { return value; } }
5.12 resolve 方法封装 比较简单直接亮代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Promise .resolve = function (value ) { return new Promise ((resolve, reject ) => { if (value instanceof Promise ) { value.then (v => { resolve (v); }, r => { reject (r); }) } else { resolve (value); } }) }
5.13 all 方法封装
index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 let p1 = new Promise ((resolve, reject ) => { setTimeout (() => { resolve ('Ok' ) }, 1000 ) }) let p2 = new Promise ((resolve, reject ) => { resolve ('success' ) }); let p3 = new Promise ((resolve, reject ) => { resolve ('dddd' ) }); let res = Promise .all ([p1, p2, p3])console .log (res);
promise.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Promise .all = function (promises ) { return new Promise ((resolve, reject ) => { let count = 0 ; let arr = []; for (let i = 0 ; i < promises.length ; i++) { promises[i].then (v => { count++; arr[i] = v; if (count === promises.length ) { resolve (arr); } }, r => { reject (r); }) } }) }
5.14 then 方法 回调的异步执行 先看场景:
index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 let p1 = new Promise ((resolve, reject ) => { resolve ('OK' ) console .log (111 ); }) p1.then (value => { console .log (222 ); }) console .log (333 );
要求我们的 then 内部的回调方法应该是异步执行。
打印结果:却是同步执行
用一个比较粗糙的方法解决,给所有的回调函数加上定时器:
总结分析: 这个尚硅谷的视频一直没讲链式调用的过程,我觉得是一个很大的遗憾,我这一块还是挺迷糊的,我尝试着自己来分析一下。
首先我们需要知道 then 本身是同步,只是它内部的回调函数是异步的。可以用代码来测试一下
index.html
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 let p1 = new Promise ((resolve, reject ) => { setTimeout (() => { resolve ('Ok' ) }, 1000 ) }) let p2 = new Promise ((resolve, reject ) => { setTimeout (() => { resolve ('success' ) }, 2000 ) }); let p3 = new Promise ((resolve, reject ) => { resolve ('dddd' ) }); p1.then (value => { console .log (value); console .log (111 ); return p2; }).then (value => { console .log (value); console .log (222 ); return p3; }).then (value => { console .log (value); console .log (333 ); }) console .log ('我得在回调之前执行' );
promise.js
修改 then 函数内容,在调用 then 时一上来就打印一下。
1 2 3 Promise .prototype .then = function (onResolved, onRejected ) { console .log ('then本身是同步的' ); }
我们可以看一下打印结果:
可以得出结论,then 的链式调用会在一开始就一下子执行下去。then 内部的回调函数是按照我们写的顺序执行下去,为啥会这样呢,我们一行一个代码逐步解析一下。
先分析一上来定义出来的三个 promise 对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let p1 = new Promise ((resolve, reject ) => { setTimeout (() => { resolve ('Ok' ) }, 1000 ) }) let p2 = new Promise ((resolve, reject ) => { setTimeout (() => { resolve ('success' ) }, 2000 ) }); let p3 = new Promise ((resolve, reject ) => { resolve ('dddd' ) });
第一步:定义 P1 是一个 promise 对象,P1 的 PromiseState 是 pending,PromiseResult 是 null,this.callback = []。然后开始调用 executor(resolve, reject) 执行器, 但执行器内部一进去就是一个定时器,定时器是异步函数调用,进异步队列,然后继续往下执行就到 P2。
第二步:定义 P2,P2 与 P1 类似, PromiseState 和 PromiseResult 都一样,executor 执行器内部也是一个定时器,进异步队列
第三步:定义 P3,因为 P3 executor 执行器 内部没有异步函数,所以直接按照同步代码执行,而且调用的是 resolve,所以返回的是成功 PromiseState 为 fulfilled, PromiseResult 为 Ok ,callback 是 异步函数,虽然里面还没有值,还是要放到异步队列中。
开始比较刺激的 then 方法调用了。
第四步:P1 调用 then 方法,这个过程是这样的,因为我们之前在 then 的第一行添加了 console.log('then本身是同步的');
,所以控制台打印 then本身就是同步的。 接着因为 P1 的 PromiseState 是 pending ,所以会将 then 中的回调函数保存到 P1 的 callback 属性上。这时 P1.then() 方法执行结束,返回一个 Promise 对象,我把该返回的 Promise 对象命名为 X。该Promise 对象正处于 pending 状态中。
第五步:链式调用 then。
这个就相当于 X 在调用 then
X 此时 是 pending 状态,调用 then 就是 先打印下 then本身是同步的 这句话,然后就是 把 then 内部的回调函数保存 X 的 callback 上。这时, X.then() 执行结束, X.then() 返回一个对象,我称之为 Y ,这个 Y 对象状态此时也是 pending。
第六步:最后一个链式调用
1 2 3 4 5 6 7 8 9 10 11 12 p1.then (value => { console .log (value); console .log (111 ); return p2; }).then (value => { console .log (value); console .log (222 ); return p3; }).then (value => { console .log (value); console .log (333 ); })
分解后相当于:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 let X = p1.then (value => { console .log (value); console .log (111 ); return p2; }) let Y = X.then (value => { console .log (value); console .log (222 ); return p3; }) let Z = Y.then (value => { console .log (value); console .log (333 ); })
就和上面一样,Y 此时是 pending 状态,所以调用 then 方法,直接将回调函数保存到 Y 的 callback 属性上,返回一个新 Promise ,状态是 pending。
这是同步代码执行完,控制台打印的结果:
到此为止,所有同步代码执行完毕,开始执行异步代码,这一部分又是老大难。。。
第七步:开始执行异步队列的代码
异步队列里有三个异步函数, 其实 setTimeout-回调-P3 这个异步函数并没有作用,因为 P3 的 callback 是空的。有作用的就 setTimeout-P1 和 setTimeout-P2。
因为只有 P1.then() 调用了,存有属于自己的回调函数,别的 P2,P3 都没有 .then() 拥有自己的回调函数。所以就算 P2 或 P3 的执行器里的 异步函数先完成,他们的 callback 也是空的,没有回调函数可以调用。
接下来直接讲解下这个调用过程。
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 let p1 = new Promise ((resolve, reject ) => { setTimeout (() => { resolve ('Ok' ) }, 1000 ) }) let p2 = new Promise ((resolve, reject ) => { setTimeout (() => { resolve ('success' ) }, 2000 ) }); let p3 = new Promise ((resolve, reject ) => { resolve ('dddd' ) }); let X = p1.then (value => { console .log (value); console .log (111 ); return p2; }) let Y = X.then (value => { console .log (value); console .log (222 ); return p3; }) let Z = Y.then (value => { console .log (value); console .log (333 ); }) console .log ('X' , X);console .log ('Y' , Y);console .log ('Z' , Z);console .log ('P1' , p1);console .log ('P2' , p2);console .log ('P3' , p3);
还没执行前所有对象的状态
我们来看一下执行的步骤过程。
第一步:先执行完 P1 的 resolve 方法(P1 异步函数在执行时会记住 P1 的环境),将 P1 的属性都成功更改为 fulfilled 和 Ok,然后开始执行 P1 callback 里保存的回调函数。
这个回调函数是
这个回调时被修饰过的,修饰后:
执行后结果是,打印 value,打印 111,将 P2 返回给 res
P1.then() 执行结束时返回了一个 Promise 对象给 X。而 res 执行的环境是Promise X 的环境
resolve 执行就是修改 X 的状态,X 更改为 fulfilled,内容为 success ,然后要执行 X 的 callback 里的回调函数。X callback里的回调函数是 X.then() 时保存的。
然后就和上面的过程一样。
最终结果:一切都顺利执行。
本质: 尚硅谷的 promise 的实现研究后发现就是在输出结果上模拟了真实 promise 的实现过程。但实现过程不是真的 promise。如果存在这么一个场景,p2 的执行需要用到 p1 完成后的结果,那尚硅谷模拟的 promise 是无法完成的。因为 尚硅谷的 promise 只是把 p1,p2,p3的结果按顺序打印出来而已,实际上三者的异步方法在执行过程中是无关的。
6. Promisification 指将一个接受回调的函数转换为一个返回 promise 的函数。
由于许多函数和库都是基于回调的,因此,在实际开发中经常会需要进行这种转换。因为使用 promise 更加方便,所以将基于回调的函数和库 promisify 是有意义的。(译注:promisify 即指 promise 化)
具体案例可以看 2.3 2.4 的练习
7. 简介 async await 7.1 async
函数的返回值为 promise 对象。
promise 对象的结果由 async 函数执行的返回值决定
7.2 await
await 右侧的表达式一般为 promise 对象,但也可以是其他的值
如果表达式 promise 对象。await 返回的是 promise 成功的值
如果表达式是其它值,直接将此值作为await 的值
7.3 实际案例1 1 2 3 4 5 6 7 8 9 10 11 12 const fs = require ('fs' )const util = require ('util' )const mineReadFile = util.promisify (fs.readFile );async function main ( ) { let data1 = await mineReadFile ('./a.txt' ); let data2 = await mineReadFile ('./b.txt' ); let data3 = await mineReadFile ('./c.txt' ); }
7.4 实际案例2 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 function sendAJAX (url ) { return new Promise ((resolve, reject ) => { const xhr = new XMLHttpRequest (); xhr.open ('GET' , url); xhr.send (); xhr.onreadystatechange = function ( ) { if (xhr.readyState === 4 ) { if (xhr.status >= 200 && xhr.status < 300 ) { resolve (xhr.response ); } else { reject (xhr.status ) } } } }) } let btn = document .querySelector ('#btn' );btn.addEventListener ('click' ,async function ( ) { let message = await sendAJAX ('https://api.apiopen.top/getJoke' ); console .log (message); })