本文最后更新于:8 个月前
为什么要有Promise、它的出现是为了解决什么问题、Promise的基本介绍
Promise特点 一、为什么要有Promise 我们在学习一个东西之前,首先得知道这个东西是用来干什么的。否则稀里糊涂学了一大堆,到最后越学越懵,别人问你一个最简单的问题Promise是用来干什么的,你憋了半天回答不出来
1.1.解决什么问题
(1)执行代码与结果处理逻辑分离,(说人话:“指定回调函数的方式更灵活”)
(2)解决“回调地狱”
第一个场景 ,我们通过setTimeout模拟一个异步操作,异步操作中生成一个随机数,结果有两种状态,这个数要么是奇数、要么是偶数,针对两种状态做不同的处理(对应到其它实际的异步操作中,就是要么成功、要么失败)
分别使用「回调」、「Promise」的方式,对不同的结果状态进行处理,来对比一下两者有什么区别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function generate_even_number (success, failure ){ setTimeout (()=> { const num = Math .ceil (Math .random ()*10 ) if (num%2 ===0 ) success (num) else failure () },1000 ) }generate_even_number ((result )=> { console .log ('even_number: ' ,result) }, ()=> { console .log ('failure' ) })
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function generate_even_number ( ){ return new Promise ((resolve, reject )=> { setTimeout (()=> { const num = Math .ceil (Math .random ()*10 ) if (num%2 ===0 ) resolve (num) else reject () }) }) }const p1 = generate_even_number () const p2 = p1.then ((result )=> {console .log ('even_num: ' ,result)})const p3 = p2.catch (()=> {console .log ('failure' )})
对比一下两者,可以看到
「回调」的方式必须在执行代码(调用generate_even_number
)的同时指定结果的处理逻辑(传入两个回调函数)
「Promise」的方式,可以先执行代码,而后再来指定对结果的处理逻辑
做到了执行代码、处理结果分离(可是这样有什么好处呢?依然没get到)
第二个场景 ,先后读取三个文本,按顺序进行打印
同样分别使用两种方式进行对比
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var fs = require ('fs' ) fs.readFile ('a.txt' ,(err,txt )=> { if (err) throw new Error (err) else { print (txt) fs.readFile ('b.txt' ,(err2,txt2 )=> { if (err2) throw new Error (err2) else { print (txt2) fs.readFile ('c.txt' ,(err3,txt3 )=> { if (err3) throw new Error (err3) print (txt3) }) } }) } })
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 var fs = require ('fs' )function readFile (url ){ return new Promise (resolve, reject){ fs.readFile (url, (err,txt )=> { if (err) reject (err) else resolve (txt) }) } }readFile ('a.txt' ).then ((result )=> { print (result) return readFile ('b.txt' ) }).then ((result )=> { print (result) return readFile ('c.txt' ) }).then ((result )=> { print (result) }).catch ((err )=> { throw new Error (err) })
通过观察可以看到,在执行串行任务时,任务数量越多,「回调」这种方式的嵌套层数就越深,代码可读性越差,所以被称为“回调地狱”。「Promise」的优势在于可以通过不断地调用then方法,进行链式调用,代码结构清晰!
1.2.缺点
无法取消Promise,一旦新建、立即执行、无法取消
如果不设置reject回调,内部抛出的错误不会反应到外部
1.3 Promise是什么 Promise实际上应该是一个规范:Promises/A+ (promisesaplus.com) 。只是各个浏览器厂商将这个规范实现出来供我们使用。
二、Promise构造器 2.1 构造器语法
2.2 executor函数 executor的函数签名为:
1 2 3 4 function (resolve, reject ){...}
如果在executor中抛出错误,该promise对象将被拒绝
executor的两项主要功能:
初始化Promise的异步行为
控制Promise实例状态的最终转换,通过resolve/reject转换
2.3 异步调用的流程
executor内执行异步操作,提供回调函数(接口)
回调通过调用resolve
终止,resolve
的调用包含一个value
参数
该value
被返回到绑定的Promise
对象上
该Promise
对象调用.then(handleResolved)
.then(handleResolved)
收到value
作为参数传递给handleResolved
进行使用
2.4 Promise用法示例 正确的初始化语法 :构造器函数中必须提供回调函数作为参数
1 2 3 var p = new Promise var p = new Promise () var p = new Promise (()=> {})
executor函数中通过resolve()和reject参数函数控制状态转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const p = new Promise ((resolve,reject )=> { setTimeout (()=> { const time = Date .now () if (time%2 == 1 ){ resolve ("成功 " +time) }else { reject ("失败 " +time) } }, 2000 ); }); p.then ( (value ) => {console .log ("成功,value:" ,value)}, (reason ) => {console .log ("失败,reason:" ,reason)} );
三、Promise的3种状态 3.1 三种状态
pending,待定。(初始化状态)可以转换为另外两种状态之一,一经转换不能再改变
resolved,解决,(有时也写做 fulfilled),带有一个value值
rejected,拒绝,带有一个reason值
1 2 3 var p1 = new Promise (()=> {}) var p2 = Promise .resolve ('解决' ) var p3 = Promise .reject ('拒绝' )
状态转换是不可逆的 。pending一旦转换为resolved或rejected之后,任何方法都无法再改变该实例的状态。
3.2 改变状态的方法
在executor
中调用resolve
/reject
转换
调用静态方法resolve
/reject
直接获得非pending状态的Promise实例
1 2 3 4 5 6 7 const p = new Promise ((resolve,reject )=> { setTimeout (()=> { const time=Date .now (); if (time%2 ==1 ){resolve ("成功" +time);} else {reject ("失败" +time);} },1000 ); });
1 2 3 4 let p1 = Promise .resolve ()let p2 = Promise .reject () p1 p2
四、Promise方法概述 4.1.静态方法 Promise静态方法定义在构造函数里,如下
以下只做列举,方法的详细细节请看这一篇
方法
说明
resolve
把任何值都转换为一个Promise,它只对第一个参数起作用
reject
实例化一个拒绝的Promise并抛出一个异步错误,只接受一个参数作为reason
all
等待一组Promise实例全部resolved之后,返回PromiseResult数组。如果有一个实例reject了,将被迫中止
allSettled
等待一组Promise实例全部resolved之后,返回PromiseResult数组。即使有实例reject了,照常执行完
race
返回一组Promise实例集合中最先落定的Promise实例
any
返回一组中最先resolved的Promise实例,只有所有实例都rejected了返回的才是rejected的实例
4.2.实例方法 Promise实例方法定义在Promise原型上
以下只做列举,方法的详细细节请看这一篇
方法
说明
then
为Promise实例添加处理程序
catch
语法糖,用于给期约添加拒绝处理程序
finally
用于给Promise实例添加onFinally
处理程序,不管实例是resolved还是rejected状态都会执行
五、Promise模拟实现(完整) 实现 Promise 最复杂的部分在于 then的链式调用和值穿透
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 class myPromise { constructor (executor ) { this .status = 'pending' this .value = undefined this .reason = undefined this .onResolvedCallbacks = [] this .onRejectedCallbacks = [] let resolve = (value ) => { if (value instanceof myPromise) { return value.then (resolve, reject) } if (this .status === 'pending' ) { this .status = 'fulfilled' this .value = value this .onResolvedCallbacks .forEach (cb => cb (this .value )) } } let reject = (reason ) => { if (this .status === 'pending' ) { this .status = 'rejected' this .reason = reason this .onRejectedCallbacks .forEach (cb => cb (this .reason )) } } try { executor (resolve, reject) } catch (err) { reject (err) } } then (onResolved, onRejected ) { onResolved = typeof onResolved === 'function' ? onResolved : v => v onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err } if (this .status === 'pending' ) { this .onResolvedCallbacks .push (onResolved) this .onRejectedCallbacks .push (onRejected) } else if (this .status === 'fulfilled' ) { onResolved (this .value ) } else { onRejected (this .reason ) } } catch (onRejected) { return this .then (null , onRejected) } static resolve (data ) { return new myPromise ((resolve, reject ) => { resolve (data) }) } static reject (reason ) { return new Promise ((resolve, reject ) => { reject (reason) }) } static all (promises ) { if (!Array .isArray (promises)) { throw new TypeError ('Promises must be an array!' ); } return new Promise ((resolve, reject ) => { let resultArr = [] let count = 0 const processResultByKey = (value, i ) => { result[i] = value if (++count === promises.length ) resolve (resultArr) } for (let i = 0 ; i < promises.length ; i++) { let promise = promises[i] promise.then (data => { processResultByKey (data, i) }, reason => { reject (reason) }) } if (promises.lenth === 0 ) { resolve ([]) } }) } static race (promises ) { if (!Array .isArray (promises)) { throw new TypeError ('Promises must be an array!' ); } return new myPromise ((resolve, reject ) => { for (let i = 0 ; i < promises.length ; i++) { let premise = promises[i] promise.then (data => { resolve (data) }, reason => { reject (reason) }) } }) } }
参考链接 Promise - 廖雪峰的官方网站 (liaoxuefeng.com)
面试官:“你能手写一个 Promise 吗” - 知乎 (zhihu.com)