面试官眼中的Promise
栏目: JavaScript · 发布时间: 5年前
内容简介:本文假设你有一定的Promise基础知识,不涉及api的讲解,但是对你深入理解Promise有一定益处。在公司顶过几天面试官,一道手写Promise就卡主了不少人(受困于这道题的人别打我。。。我是不会告诉你我就职的公司的),其实这道题的主要目的是考察对Promise的理解,顺便的才是考察js逻辑,写出来是加分项,能表达出你对Promise的理解才是最重要的。但是现实情况是有挺多人直接白卷,把加分项变成了减分项。写不写是态度问题,写出几个点已经足以让面试官高看你一眼。下面我就把面试官希望看到几个点拆解出来,
本文假设你有一定的Promise基础知识,不涉及api的讲解,但是对你深入理解Promise有一定益处。
写在前面
在公司顶过几天面试官,一道手写Promise就卡主了不少人(受困于这道题的人别打我。。。我是不会告诉你我就职的公司的),其实这道题的主要目的是考察对Promise的理解,顺便的才是考察js逻辑,写出来是加分项,能表达出你对Promise的理解才是最重要的。
但是现实情况是有挺多人直接白卷,把加分项变成了减分项。写不写是态度问题,写出几个点已经足以让面试官高看你一眼。下面我就把面试官希望看到几个点拆解出来,以面试题的方式去理解Promise,希望对你们有所帮助。
不想看长篇大论的可以直接查看总结:
Promise捕获错误与 try catch 等同
- 请写出下列代码的输出
var p1 = new Promise(function(resolve, reject) { throw Error('sync error') }) .then(res => { console.log(res) }) .catch(err => { console.log(err) }) 复制代码
2.请写出下列代码的输出
var p1 = new Promise(function(resolve, reject) { setTimeout(() => { throw Error('async error') }) }) .then(res => { console.log(res) }) .catch(err => { console.log(err) }) 复制代码
- 请写出下列代码的输出
var p1 = new Promise(function(resolve, reject) { resolve() }) .then(res => { throw Error('sync error') }) 复制代码
错误三连,你知道正确答案吗:smirk:?
正确答案是:
- Error被catch到,最后console.log输出
- 错误无法被catch,控制台报错
- promise没有catch,错误被捕获后又被抛出,控制台报错
这里考查的主要是Promise的错误捕获,其实仔细想想js中能用的错误捕获也只能是try catch了,而try catch只能捕获同步错误,并且在没有传入错误监听的时候会将捕获到的错误抛出。
所以在手写promise中,你至少要写出try catch包裹回调代调
function Promise(fn) { ... doResolve(fn, this) } function doResolve(fn, self) { try { fn(function(value) { ... }, function(reason) { ... }) } catch(err) { reject(self, err) } } Promise.prototype.then = function(onFulfilled, onRejected) { try { ... onFulfilled(value) } catch(err) { reject(err) } }; function reject(self, newValue) { ... if (!self._handled) { Promise._unhandledRejectionFn(self._value); } } 复制代码
Promise 拥有状态变化
把上面的面试题改写一下:
- 请写出下列代码的输出
var p1 = new Promise(function(resolve, reject) { resolve(1) throw Error('sync error') }) .then(res => { console.log(res) }) .catch(err => { console.log(err) }) 复制代码
- 请写出下列代码的输出
var p1 = new Promise(function(resolve, reject) { reject(2) resolve(1) }) .then(res => { console.log(res) }) .catch(err => { console.log(err) }) 复制代码
- 请写出下列代码的输出
var p1 = new Promise(function(resolve, reject) { resolve(1) }) .then(res => { throw Error('sync error') console.log(res) }) .catch(err => { console.log(err) }) 复制代码
正确答案是:
- 输出 1
- 输出 2
- console.log输出错误
Promise是一个有状态的容器,当状态被凝固了,后面的resolve或reject就不会被触发。简单的说就是同一个Promise只能触发一个状态监听(onFulfilled或onRejected)。所以在手写Promise中需要有一个状态标记:
function Promise(fn) { ... this._state = 0 // 状态标记 doResolve(fn, this) } function doResolve(fn, self) { var done = false // 保证只执行一个监听 try { fn(function(value) { if (done) return done = true resolve(self, value) }, function(reason) { if (done) return; done = true reject(self, value) }) } catch(err) { if (done) return done = true reject(self, err) } } function resolve(self, newValue) { try { self._state = 1; ... } catch(err) { reject(self, err) } } function reject(self, newValue) { self._state = 2; ... if (!self._handled) { Promise._unhandledRejectionFn(self._value); } } 复制代码
Promise 方法中的回调是异步的
- 请写出下列代码的输出
var p1 = new Promise(function(resolve, reject) { resolve() setTimeout(() => { console.log(1) }) console.log(2) }) .then(res => { console.log(3) }) console.log(4) 复制代码
正确答案是:
依次输出:
2 4 3 1 复制代码
或
2 4 1 3 复制代码
首先 promise 中then、catch、finally中的回调都是异步执行的,所以前面输出 2 4
的同步代码是没有疑问的。
那为什么两种答案都认为是对的呢,其实是因为polyfill的锅。在chrome中使用原生Promise会将then(catch/finally)中的回调置为优先异步,类似于node中的nextTick,所以正常的输出应该是 2 4 3 1
。但是在浏览器环境中是无法使用nextTick的,所以浏览器中的polyfill使用setTimeout,最终会输出 2 4 1 3
。
那么手写Promise中,应该将resolve,reject回调设为异步:
function handle(self, deferred) { ... setTimeout(function() { var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected; if (cb === null) { (self._state === 1 ? resolve : reject)(deferred.promise, self._value); return; } var ret; try { ret = cb(self._value); } catch (e) { reject(deferred.promise, e); return; } resolve(deferred.promise, ret); }, 0) ... } 复制代码
Promise 会存储返回值
- 请写出下列代码的输出
var p1 = new Promise(function(resolve, reject) { reject(1) }) .catch(err => { console.log(err) return 2 }) setTimeout(() => { p1 .then(res => console.log(res)) }, 1000) 复制代码
正确答案是:
先输出 1
1秒后输出 2
Promise会将最后的值存储起来,如果在下次使用promise方法的时候回直接返回该值的promise。
所以手写一个Promise,你应该保存返回值:
function Promise(fn) { ... this._state = 0 // 状态标记 this._value = undefined; // 存储返回值 doResolve(fn, this) } function resolve(self, newValue) { try { ... if (newValue instanceof Promise) { self._state = 3; self._value = newValue; finale(self); return; } else if (typeof then === 'function') { doResolve(bind(then, newValue), self); return; } self._state = 1; self._value = newValue; ... } catch(err) { reject(self, err) } } function reject(self, newValue) { self._state = 2; self._value = newValue; ... if (!self._handled) { Promise._unhandledRejectionFn(self._value); } } 复制代码
Promise 方法每次都返回一个新的Promise
- 请写出下列代码的输出
var p1 = new Promise(function(resolve, reject) { reject(1) }) .then( res => { console.log(res) return 2 }, err => { console.log(err) return 3 } ) .catch(err => { console.log(err) return 4 }) .finally(res => { console.log(res) return 5 }) .then( res => console.log(res), err => console.log(err) ) 复制代码
正确答案是:
依次输出:
1 undefined 3 复制代码
Promise能够链式调用的原因是它的每一个方法都返回新的promise,哪怕是finally方法,特殊的是fanilly会返回上一个promise的值包装成的新promise,并且finally也不接收参数,因为无论Promise是reject还是fulfill它都会被调用。
所以你需要在promise方法中返回新的promise:
function bind(fn, thisArg) { return function() { fn.apply(thisArg, arguments); }; } function resolve(self, newValue) { ... try { if (newValue instanceof Promise) { self._state = 3; self._value = newValue; finale(self); return; } else if (typeof then === 'function') { doResolve(bind(then, newValue), self); return; } self._state = 1; ... } catch (e) { reject(self, e); } } 复制代码
总结
上述总共表达了五个Promise知识点:
- Promise捕获错误与 try catch 等同
- Promise 拥有状态变化
- Promise 方法中的回调是异步的
- Promise 方法每次都返回一个新的Promise
- Promise 会存储返回值
文中案例皆取自 promise-polyfill ,有美玉在前,作者就不亮出自己的板砖了,同时也提醒各位面试者多看优秀作品的源码,何必看那些不太正规的第三方的实现。
毕竟公司的目标不是造重复的轮子,如果你已经能清晰明了地表述出上述部分知识,我们就能相信你已经是一个能够正确并灵活使用Promise的开发者了,及格分双手奉上(以我们公司的招聘目标为例,相信大部分公司要求也是如此)。
最后:
马上快到2019年了,祝大家都能找到称心如意的工作!:tada::tada::tada:
-- The End
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Python面试经验总结,面试一时爽,一直面试一直爽!
- 算法面试:数组编码面试问题
- 【面试虐菜】—— JAVA面试题(1)
- 如何面试-作为面试官得到的经验
- PHP面试之网络协议面试题
- 如何克服面试紧张心理 ?(面试答题篇Ⅲ)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。