Promise基本上现在不管是大厂还是小厂,promise 已经成为了面试必考知识点;关于 Promise,想必大家都又所了解,可是又掌握了多少,真正面试的时候,又能有多少把握呢?
常见面试题 首先,我们以常见的 Promise
面试题为切入点,我们看看面试官们都爱考什么:
这几个问题由浅入深,我们一个一个来看:
Promise 出现的原因 在 Promise
出现以前,在我们处理多个异步请求嵌套时,代码往往是这样的。。。
1 2 3 4 5 6 7 8 9 let fs = require ('fs' )fs.readFile('./name.txt' ,'utf8' ,function (err,data ) { fs.readFile(data, 'utf8' ,function (err,data ) { fs.readFile(data,'utf8' ,function (err,data ) { console .log(data); }) }) })
为了拿到回调的结果,我们必须一层一层的嵌套,可以说是相当恶心了。而且基本上我们还要对每次请求的结果进行一系列的处理,使得代码变的更加难以阅读和难以维护,这就是传说中臭名昭著的 回调地狱
, 产生 回调地狱
的原因归结起来有两点:
嵌套调用
,第一个函数的输出往往是第二个函数的输入;
处理多个异步请求并发
,开发时往往需要同步请求最终的结果。
原因分析出来后,那么问题的解决思路就很清晰了:
消灭嵌套调用
:通过 Promise
的链式调用可以解决 .then()
;
合并多个任务的请求结果
:使用 Promise.all
获取合并多个任务的错误处理。
Promise 正是用一种更加友好的代码组织方式,解决了异步嵌套的问题。
我们来看看上面的例子用 Promise 实现是什么样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 let fs = require ('fs' )function read (filename ) { return new Promise ((resolve, reject ) => { fs.readFile(filename, 'utf8' , (err, data ) => { if (err) reject(err); resolve(data); }) }) } read('./name.txt' ).then((data )=> { return read(data) }).then((data )=> { return read(data) }).then((data )=> { console .log(data); },err => { console .log(err); })
臃肿的嵌套变得线性多了有木有?没错,他就是我们的异步神器 Promise
!
让我们再次回归刚才的问题,Promise
为我们解决了什么问题?在传统的异步编程中,如果异步之间存在依赖关系,就需要通过层层嵌套回调的方式满足这种依赖,如果嵌套层数过多,可读性和可以维护性都会变得很差,产生所谓的 “回调地狱”,而 Promise
将嵌套调用改为链式调用,增加了可阅读性和可维护性。也就是说,Promise
解决的是异步编码风格的问题。
Promise 基本特征 promise
有三个状态:pending
,fulfilled
,or rejected
;
new promise
时, 需要传递一个 executor()
执行器,执行器立即执行;
executor
接受两个参数,分别是 resolve
和 reject
;
promise
的默认状态是 pending
;
promise
只能从 pending
到 rejected
, 或者从 pending
到 fulfilled
,状态一旦确认,就不会再改变;
promise
必须有一个 then
方法,then
接收两个参数,分别是 promise
成功的回调 onFulfilled
, 和 promise
失败的回调 onRejected
;
then
方法的执行结果也会返回一个 Promise
对象。因此我们可以进行 then
的链式执行,这也是解决回调地狱的主要方式。
如果调用 then
时,promise
已经成功,则执行 onFulfilled
,参数是 promise
的 value
如果调用 then
时,promise
已经失败,那么执行 onRejected
, 参数是 promise
的 reason
如果 then
中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then
的失败的回调 onRejected
下面就通过例子进一步讲解。
1 2 3 4 5 6 7 8 9 var promise = new Promise (function (resolve, reject ) { if () { resolve(data); } else { reject(error); } });
Promise
实例生成以后,可以用 then
方法指定 resolved
状态和 reject
状态的回调函数。
1 2 3 4 5 6 7 promise.then(onFulfilled, onRejected); promise.then(function (data ) { }, function (error ) { });
而 then
方法中指定的回调函数,将在当前脚本所有同步任务执行完才会执行。如下例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var promise = new Promise (function (resolve, reject ) { console .log('before resolved' ); resolve(); console .log('after resolved' ); }); promise.then(function ( ) { console .log('resolved' ); }); console .log('outer' );-------output------- before resolved after resolved outer resolved
Promise 常用 API
Promise.resolve()
默认产生一个成功的 promise
。
1 2 3 4 5 static resolve (data ) { return new Promise ((resolve,reject )=> { resolve(data); }) }
Promise.reject()
默认产生一个失败的 promise
,Promise.reject
是直接将值变成错误结果。
1 2 3 4 5 static reject (reason ) { return new Promise ((resolve,reject )=> { reject(reason); }) }
Promise.prototype.catch()
用来捕获 promise
的异常,就相当于一个没有成功的 then
。
1 2 3 Promise .prototype.catch = function (errCallback ) { return this .then(null ,errCallback) }
Promise.prototype.finally()
finally
表示不是最终的意思,而是无论如何都会执行的意思。 如果返回一个 promise
会等待这个 promise
也执行完毕。如果返回的是成功的 promise
,会采用上一次的结果;如果返回的是失败的 promise
,会用这个失败的结果,传到 catch
中。
1 2 3 4 5 6 7 Promise .prototype.finally = function (callback ) { return this .then((value ) => { return Promise .resolve(callback()).then(() => value) },(reason ) => { return Promise .resolve(callback()).then(() => {throw reason}) }) }
Promise.all()
是解决并发问题的,多个异步并发获取最终的结果(如果有一个失败则失败)。
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 Promise .all = function (values ) { if (!Array .isArray(values)) { const type = typeof values; return new TypeError (`TypeError: ${type} ${values} is not iterable` ) } return new Promise ((resolve, reject ) => { let resultArr = []; let orderIndex = 0 ; const processResultByKey = (value, index ) => { resultArr[index] = value; if (++orderIndex === values.length) { resolve(resultArr) } } for (let i = 0 ; i < values.length; i++) { let value = values[i]; if (value && typeof value.then === 'function' ) { value.then((value ) => { processResultByKey(value, i); }, reject); } else { processResultByKey(value, i); } } }); } let p1 = new Promise ((resolve, reject ) => { setTimeout (() => { resolve('ok1' ); }, 1000 ); }) let p2 = new Promise ((resolve, reject ) => { setTimeout (() => { reject('ok2' ); }, 1000 ); }) Promise .all([1 ,2 ,3 ,p1,p2]).then(data => { console .log('resolve' , data); }, err => { console .log('reject' , err); })
Promise.race()
用来处理多个请求,采用最快的(谁先完成用谁的)。
手写一个符合 Promise/A+ 规范的 Promise 依据 promise
的基本特征,我们试着勾勒下 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 const PENDING = 'PENDING' ;const FULFILLED = 'FULFILLED' ;const REJECTED = 'REJECTED' ;class Promise { constructor (executor ) { this .status = PENDING; this .value = undefined ; this .reason = undefined ; let resolve = (value ) => { if (this .status === PENDING) { this .status = FULFILLED; this .value = value; } } let reject = (reason ) => { if (this .status === PENDING) { this .status = REJECTED; this .reason = reason; } } try { executor(resolve,reject) } catch (error) { reject(error) } } then (onFulfilled, onRejected ) { if (this .status === FULFILLED) { onFulfilled(this .value) } if (this .status === REJECTED) { onRejected(this .reason) } } }
写完我们可以测试一下:
1 2 3 4 5 6 7 8 9 10 const promise = new Promise ((resolve, reject ) => { resolve('成功' ); }).then( (data) => { console .log('success' , data) }, (err) => { console .log('faild' , err) } )
控制台输出:
现在我们已经实现了一个基础版的 Promise
,但是还不要高兴的太早噢,这里我们只处理了同步操作的 promise
。如果在 executor()
中传入一个异步操作的话呢,我们试一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 const promise = new Promise ((resolve, reject ) => { setTimeout (() => { resolve('成功' ); },1000 ); }).then( (data) => { console .log('success' , data) }, (err) => { console .log('faild' , err) } )
执行测试脚本后发现,promise
没有任何返回。
因为 promise 调用 then 方法时,当前的 promise 并没有成功,一直处于 pending 状态。所以如果当调用 then 方法时,当前状态是 pending,我们需要先将成功和失败的回调分别存放起来,在executor()的异步任务被执行时,触发 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 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 const PENDING = 'PENDING' ;const FULFILLED = 'FULFILLED' ;const REJECTED = 'REJECTED' ;class Promise { constructor (executor ) { this .status = PENDING; this .value = undefined ; this .reason = undefined ; this .onResolvedCallbacks = []; this .onRejectedCallbacks= []; let resolve = (value ) => { if (this .status === PENDING) { this .status = FULFILLED; this .value = value; this .onResolvedCallbacks.forEach(fn => fn()); } } let reject = (reason ) => { if (this .status === PENDING) { this .status = REJECTED; this .reason = reason; this .onRejectedCallbacks.forEach(fn => fn()); } } try { executor(resolve,reject) } catch (error) { reject(error) } } then (onFulfilled, onRejected ) { if (this .status === FULFILLED) { onFulfilled(this .value) } if (this .status === REJECTED) { onRejected(this .reason) } if (this .status === PENDING) { this .onResolvedCallbacks.push(() => { onFulfilled(this .value) }); this .onRejectedCallbacks.push(()=> { onRejected(this .reason); }) } } }
测试一下:
1 2 3 4 5 6 7 8 9 10 11 12 const promise = new Promise ((resolve, reject ) => { setTimeout (() => { resolve('成功' ); },1000 ); }).then( (data) => { console .log('success' , data) }, (err) => { console .log('faild' , err) } )
控制台等待 1s 后输出:
ok!大功告成,异步问题已经解决了!
熟悉设计模式的同学,应该意识到了这其实是一个 发布订阅模式
,这种收集依赖 -> 触发通知 -> 取出依赖执行的方式,被广泛运用于发布订阅模式的实现。
then 的链式调用&值穿透特性
我们都知道,promise 的优势在于可以链式调用。在我们使用 Promise 的时候,当 then 函数中 return 了一个值,不管是什么值,我们都能在下一个 then 中获取到,这就是所谓的then 的链式调用。而且,当我们不在 then 中放入参数,例:promise.then().then(),那么其后面的 then 依旧可以得到之前 then 返回的值,这就是所谓的值的穿透。那具体如何实现呢?简单思考一下,如果每次调用 then 的时候,我们都重新创建一个 promise 对象,并把上一个 then 的返回结果传给这个新的 promise 的 then 方法,不就可以一直 then 下去了么?那我们来试着实现一下。这也是手写 Promise 源码的重中之重,所以,打起精神来,重头戏来咯!
有了上面的想法,我们再结合 Promise/A+
规范梳理一下思路:
then
的参数 onFulfilled
和 onRejected
可以缺省,如果 onFulfilled
或者 onRejected
不是函数,将其忽略,且依旧可以在下面的 then
中获取到之前返回的值;
promise
可以 then
多次,每次执行完 promise.then
方法后返回的都是一个 “新的promise”;
如果 then
的返回值 x 是一个普通值,那么就会把这个结果作为参数,传递给下一个 then
的成功的回调中;
如果 then
中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then
的失败的回调中;
如果 then
的返回值 x 是一个 promise
,那么会等这个 promise
执行完,promise
如果成功,就走下一个 then
的成功;如果失败,就走下一个 then
的失败;如果抛出异常,就走下一个 then
的失败;
如果 then
的返回值 x 和 promise
是同一个引用对象,造成循环引用,则抛出异常,把异常传递给下一个 then
的失败的回调中;
如果 then
的返回值 x 是一个 promise
,且 x 同时调用 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 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 const PENDING = 'PENDING' ;const FULFILLED = 'FULFILLED' ;const REJECTED = 'REJECTED' ;const resolvePromise = (promise2, x, resolve, reject ) => { if (promise2 === x) { return reject(new 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(promise2, 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 .reason = undefined ; this .onResolvedCallbacks = []; this .onRejectedCallbacks= []; let resolve = (value ) => { if (this .status === PENDING) { this .status = FULFILLED; this .value = value; this .onResolvedCallbacks.forEach(fn => fn()); } } let reject = (reason ) => { if (this .status === PENDING) { this .status = REJECTED; this .reason = reason; this .onRejectedCallbacks.forEach(fn => fn()); } } try { executor(resolve,reject) } catch (error) { reject(error) } } then (onFulfilled, onRejected ) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v; onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }; let promise2 = new Promise ((resolve, reject ) => { if (this .status === FULFILLED) { setTimeout (() => { try { let x = onFulfilled(this .value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e) } }, 0 ); } if (this .status === REJECTED) { setTimeout (() => { try { let x = onRejected(this .reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e) } }, 0 ); } if (this .status === PENDING) { this .onResolvedCallbacks.push(() => { setTimeout (() => { try { let x = onFulfilled(this .value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e) } }, 0 ); }); this .onRejectedCallbacks.push(()=> { setTimeout (() => { try { let x = onRejected(this .reason); resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }, 0 ); }); } }); return promise2; } }
测试一下:
1 2 3 4 5 6 7 const promise = new Promise ((resolve, reject ) => { reject('失败' ); }).then().then().then(data => { console .log(data); },err => { console .log('err' ,err); })
控制台输出:
至此,我们已经完成了 promise
最关键的部分:then
的链式调用和值的穿透。搞清楚了 then
的链式调用和值的穿透,你也就搞清楚了 Promise
。
Promise 有什么缺陷,可以如何解决?
Promise 虽然跳出了异步嵌套的怪圈,用链式表达更加清晰,但是我们也发现如果有大量的异步请求的时候,流程复杂的情况下,会发现充满了屏幕的 then,看起来非常吃力,而 Async/Await 的出现就是为了解决这种复杂的情况。
优缺点:
代码简洁,不用 then
,避免了嵌套代码
错误处理,Async/Await
让 try/catch
可以同时处理同步和异步错误,在下面的 promise
示例中,try/catch
不能处理 JSON.parse
的错误,因为它在 Promise
中。我们需要使用 .catch
,这样错误处理代码非常冗余。并且,在我们的实际生产代码会更加复杂。
中间值不好操作
调试困难
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const makeRequest = () => { try { getJSON().then(result => { const data = JSON .parse(result) console .log(data) }) } catch (err) { console .log(err) } } const makeRequest = async () => { try { const data = JSON .parse(await getJSON()) console .log(data) } catch (err) { console .log(err) } }
Async/Await
的增加,可以让接口按顺序异步获取数据,用更可读,可维护的方式处理回调。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 async function demo01 ( ) { return 123 ; } demo01().then(val => { console .log(val); }); async function errorDemoSuper ( ) { try { let result = await sleep(1000 ); console .log(result); } catch (err) { console .log(err); } }
取个应用的案列
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 handleInputInviteCode ( ) { this .$store.dispatch('walletStore/verifyInviteCode' , { inviteCode: this .inviteCode }).then(res => { this .$emit('confirm' ) }).catch(error => { this .$emit('failure' ) }) } verifyInviteCode ({commit,state,dispatch}, {inviteCode, header, id} ) { return new Promise (async (resolve, reject) => { try { const res = await handleRequest.post(`/api/core/invite/activation/${inviteCode} ` , {}, header) if (res.status === 200 && res.data === true ){ let form = await handleGetUserInfo(currentWallet) await commit('changeWallet' , form) resolve(form) return } reject(LANG_DATA[res.code] || res.code) } catch (e){ reject(e.message || e.code || '激活失败,请检查激活码' ) } }) }