面试官眼中的Promise

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

内容简介:本文假设你有一定的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


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

查看所有标签

猜你喜欢:

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

jQuery实战(第2版)

jQuery实战(第2版)

[美]Bear Bibeault、[美]Yehuda Katz / 三生石上 / 人民邮电出版社 / 2012-3 / 69.00元

jQuery 是目前最受欢迎的JavaScript/Ajax 库之一,能用最少的代码实现最多的功能。本书全面介绍jQuery 知识,展示如何遍历HTML 文档、处理事件、执行动画、给网页添加Ajax 以及jQuery UI 。书中紧紧地围绕“用实际的示例来解释每一个新概念”这一宗旨,生动描述了jQuery 如何与其他工具和框架交互以及如何生成jQuery 插件。 本书适合各层次Web 开发人......一起来看看 《jQuery实战(第2版)》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具