面试官眼中的Promise

栏目: JavaScript · 发布时间: 7年前

内容简介:本文假设你有一定的Promise基础知识,不涉及api的讲解,但是对你深入理解Promise有一定益处。在公司顶过几天面试官,一道手写Promise就卡主了不少人(受困于这道题的人别打我。。。我是不会告诉你我就职的公司的),其实这道题的主要目的是考察对Promise的理解,顺便的才是考察js逻辑,写出来是加分项,能表达出你对Promise的理解才是最重要的。但是现实情况是有挺多人直接白卷,把加分项变成了减分项。写不写是态度问题,写出几个点已经足以让面试官高看你一眼。下面我就把面试官希望看到几个点拆解出来,

本文假设你有一定的Promise基础知识,不涉及api的讲解,但是对你深入理解Promise有一定益处。

写在前面

在公司顶过几天面试官,一道手写Promise就卡主了不少人(受困于这道题的人别打我。。。我是不会告诉你我就职的公司的),其实这道题的主要目的是考察对Promise的理解,顺便的才是考察js逻辑,写出来是加分项,能表达出你对Promise的理解才是最重要的。

但是现实情况是有挺多人直接白卷,把加分项变成了减分项。写不写是态度问题,写出几个点已经足以让面试官高看你一眼。下面我就把面试官希望看到几个点拆解出来,以面试题的方式去理解Promise,希望对你们有所帮助。

不想看长篇大论的可以直接查看总结:

Promise捕获错误与 try catch 等同

  1. 请写出下列代码的输出
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)
    })
复制代码
  1. 请写出下列代码的输出
var p1 = new Promise(function(resolve, reject) {
    resolve()
})
    .then(res => {
        throw Error('sync error') 
    })
复制代码

错误三连,你知道正确答案吗:smirk:?

正确答案是:

  1. Error被catch到,最后console.log输出
  2. 错误无法被catch,控制台报错
  3. 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 拥有状态变化

把上面的面试题改写一下:

  1. 请写出下列代码的输出
var p1 = new Promise(function(resolve, reject) {
    resolve(1)
    throw Error('sync error')
})
    .then(res => {
        console.log(res)
    })
    .catch(err => {
        console.log(err)
    })
复制代码
  1. 请写出下列代码的输出
var p1 = new Promise(function(resolve, reject) {
    reject(2)
    resolve(1)
})
    .then(res => {
        console.log(res)
    })
    .catch(err => {
        console.log(err)
    })
复制代码
  1. 请写出下列代码的输出
var p1 = new Promise(function(resolve, reject) {
    resolve(1)
})
    .then(res => {
        throw Error('sync error')
        console.log(res)
    })
    .catch(err => {
        console.log(err)
    })
复制代码

正确答案是:

  1. 输出 1
  2. 输出 2
  3. 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 方法中的回调是异步的

  1. 请写出下列代码的输出
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 会存储返回值

  1. 请写出下列代码的输出
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

  1. 请写出下列代码的输出
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知识点:

  1. Promise捕获错误与 try catch 等同
  2. Promise 拥有状态变化
  3. Promise 方法中的回调是异步的
  4. Promise 方法每次都返回一个新的Promise
  5. Promise 会存储返回值

文中案例皆取自 promise-polyfill ,有美玉在前,作者就不亮出自己的板砖了,同时也提醒各位面试者多看优秀作品的源码,何必看那些不太正规的第三方的实现。

毕竟公司的目标不是造重复的轮子,如果你已经能清晰明了地表述出上述部分知识,我们就能相信你已经是一个能够正确并灵活使用Promise的开发者了,及格分双手奉上(以我们公司的招聘目标为例,相信大部分公司要求也是如此)。

最后:

马上快到2019年了,祝大家都能找到称心如意的工作!:tada::tada::tada:

-- The End


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Rework

Rework

Jason Fried、David Heinemeier Hansson / Crown Business / 2010-3-9 / USD 22.00

"Jason Fried and David Hansson follow their own advice in REWORK, laying bare the surprising philosophies at the core of 37signals' success and inspiring us to put them into practice. There's no jarg......一起来看看 《Rework》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

在线进制转换器
在线进制转换器

各进制数互转换器