Google大牛分享的面试秘籍
if (typeof Promise === 'undefined') {
return
}
实现 Promise/A+ 规范的库有很多,lie 是一个精简的实现 Promise/A+ 的库,并且通过了 Promise/A+ 专门的测试集,但 lie 的代码写的有点绕,我在 lie 的代码基础上进行了修改,使之更容易阅读和理解,并发布了 appoint 模块供大家参考。
Promise/A+ 规范
Promise 规范有很多,如 Promise/A,Promise/B,Promise/D 以及 Promise/A 的升级版 Promise/A+,有兴趣的可以去了解下,最终 ES6 中采用了 Promise/A+ 规范。在讲解 Promise 实现之前,当然要先了解 Promise/A+ 规范。Promise/A+ 规范参考:
注意:没有特殊说明以下 promise 均指代 Promise 实例。
规范虽然不长,但细节也比较多,我挑出几个要点简单说明下:
1、Promise 本质是一个状态机。每个 promise 只能是 3 种状态中的一种:pending、fulfilled 或 rejected。状态转变只能是 pending -> fulfilled 或者 pending -> rejected。状态转变不可逆。
2、then 方法可以被同一个 promise 调用多次。
3、then 方法必须返回一个 promise。规范里没有明确说明返回一个新的 promise 还是复用老的 promise(即 return this),大多数实现都是返回一个新的 promise,而且复用老的 promise 可能改变内部状态,这与规范也是相违背的。
4、值穿透。下面会细讲。
从头实现 Promise
我们知道 Promise 是一个构造函数,需要用 new 调用,并有以下几个 api:
function Promise(resolver) {} Promise.prototype.then = function() {}
Promise.prototype.catch = function() {} Promise.resolve = function() {}
Promise.reject = function() {}
Promise.all = function() {}
Promise.race = function() {}
下面我们以 appoint 为最终目标,开始一步一步构建完整的 Promise 实现。
'use strict'; var immediate = require('immediate'); function INTERNAL() {}
function isFunction(func) {
return typeof func === 'function';
}
function isObject(obj) {
return typeof obj === 'object';
}
function isArray(arr) {
return Object.prototype.toString.call(arr) === '[object Array]';
} var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2; module.exports = Promise; function Promise(resolver) {
if (!isFunction(resolver)) {
throw new TypeError('resolver must be a function');
}
this.state = PENDING;
this.value = void 0;
this.queue = [];
if (resolver !== INTERNAL) {
safelyResolveThen(this, resolver);
}
}
immediate 是一个将同步转异步执行的库。INTERNAL 就是一个空函数,类似于一些代码库中的 noop。定义了 3 个辅助函数:isFunction、isObject 和 isArray。定义了 3 种状态:PENDING、FULFILLED 和 REJECTED。safelyResolveThen 后面讲。promise 内部有三个变量:
1、state: 当前 promise 的状态,初始值为 PENDING。状态改变只能是 PENDING -> FULFILLED 或 PENDING -> REJECTED。
2、value: 当 state 是 FULFILLED 时存储返回值,当 state 是 REJECTED 时存储错误。
3、queue: promise 内部的回调队列,这是个什么玩意儿?为什么是一个数组?
Promise 实现基本原理
先看一段代码:
var Promise = require('appoint')
var promise = new Promise((resolve) => {
setTimeout(() => {
resolve('haha')
}, 1000)
})
var a = promise.then(function onSuccess() {})
var b = promise.catch(function onError() {})
console.dir(promise, { depth: 10 })
console.log(promise.queue[0].promise === a)
console.log(promise.queue[1].promise === b)
打印出:
Promise {
state: 0,
value: undefined,
queue:
[ QueueItem {
promise: Promise { state: 0, value: undefined, queue: [] },
callFulfilled: [Function],
callRejected: [Function] },
QueueItem {
promise: Promise { state: 0, value: undefined, queue: [] },
callFulfilled: [Function],
callRejected: [Function] } ] }
true
true
可以看出,queue 数组中有两个对象。因为规范中规定:then 方法可以被同一个 promise 调用多次。上例中在调用 .then 和 .catch 时 promise 并没有被 resolve,所以将 .then 和 .catch 生成的新 promise(a 和 b) 和正确时的回调(onSuccess 包装成 callFulfilled)和错误时的回调(onError 包装成 callRejected)生成一个 QueueItem 实例并 push 到 queue 数组里,所以上面两个 console.log 打印 true。当 promise 状态改变时遍历内部 queue 数组,统一执行成功(FULFILLED -> callFulfilled)或失败(REJECTED -> callRejected)的回调(传入 promise 的 value 值),生成的结果分别设置 a 和 b 的 state 和 value,这就是 Promise 实现的基本原理。
再来看另一个例子:
var Promise = require('appoint')
var promise = new Promise((resolve) => {
setTimeout(() => {
resolve('haha')
}, 1000)
})
promise
.then(() => {})
.then(() => {})
.then(() => {})
console.dir(promise, { depth: 10 })
打印出:
Promise {
state: 0,
value: undefined,
queue:
[ QueueItem {
promise:
Promise {
state: 0,
value: undefined,
queue:
[ QueueItem {
promise:
Promise {
state: 0,
value: undefined,
queue:
[ QueueItem {
promise: Promise { state: 0, value: undefined, queue: [] },
callFulfilled: [Function],
callRejected: [Function] } ] },
callFulfilled: [Function],
callRejected: [Function] } ] },
callFulfilled: [Function],
callRejected: [Function] } ] }
调用了 3 次 then,每个 then 将它生成的 promise 放到了调用它的 promise 队列里,形成了 3 层调用关系。当最外层的 promise 状态改变时,遍历它的 queue 数组调用对应的回调,设置子 promise 的 state 和 value 并遍历它的 queue 数组调用对应的回调,然后设置孙 promise 的 state 和 value 并遍历它的 queue 数组调用对应的回调......依次类推。
safelyResolveThen
function safelyResolveThen(self, then) {
var called = false;
try {
then(function (value) {
if (called) {
return;
}
called = true;
doResolve(self, value);
}, function (error) {
if (called) {
return;
}
called = true;
doReject(self, error);
});
} catch (error) {
if (called) {
return;
}
called = true;
doReject(self, error);
}
}
safelyResolveThen 顾名思义用来『安全的执行 then 函数』,这里的 then 函数指『第一个参数是 resolve 函数第二个参数是 reject 函数的函数』,如下两种情况:
1、构造函数的参数,即这里的 resolver:
new Promise(function resolver(resolve, reject) {
setTimeout(() => {
resolve('haha')
}, 1000)
})
2、promise 的 then:
promise.then(resolve, reject)
safelyResolveThen 有 3 个作用:
1、try...catch 捕获抛出的异常,如:
new Promise(function resolver(resolve, reject) {
throw new Error('Oops')
})
2、called 控制 resolve 或 reject 只执行一次,多次调用没有任何作用。即:
var Promise = require('appoint')
var promise = new Promise(function resolver(resolve, reject) {
setTimeout(() => {
resolve('haha')
}, 1000)
reject('error')
})
promise.then(console.log)
promise.catch(console.error)
打印 error,不会再打印 haha。
3、没有错误则执行 doResolve,有错误则执行 doReject。
doResolve 和 doReject
function doResolve(self, value) {
try {
var then = getThen(value);
if (then) {
safelyResolveThen(self, then);
} else {
self.state = FULFILLED;
self.value = value;
self.queue.forEach(function (queueItem) {
queueItem.callFulfilled(value);
});
}
return self;
} catch (error) {
return doReject(self, error);
}
} function doReject(self, error) {
self.state = REJECTED;
self.value = error;
self.queue.forEach(function (queueItem) {
queueItem.callRejected(error);
});
return self;
}
doReject 就是设置 promise 的 state 为 REJECTED,value 为 error,callRejected 如前面提到的通知子 promise:『我这里出了点问题呀』然后子 promise 根据传入的错误设置自己的状态和值。doResolve 结合 safelyResolveThen 使用不断地解包 promise,直至返回值是非 promise 对象后,设置 promise 的状态和值,然后通知子 promise:『我这里有值了哟』然后子 promise 根据传入的值设置自己的状态和值。
这里有个辅助函数 getThen:
function getThen(obj) {
var then = obj && obj.then;
if (obj && (isObject(obj) || isFunction(obj)) && isFunction(then)) {
return function appyThen() {
then.apply(obj, arguments);
};
}
}
规范中规定:如果 then 是函数,将 x(这里是 obj) 作为函数的 this 调用。
Promise.prototype.then 和 Promise.prototype.catch
Promise.prototype.then = function (onFulfilled, onRejected) {
if (!isFunction(onFulfilled) && this.state === FULFILLED ||
!isFunction(onRejected) && this.state === REJECTED) {
return this;
}
var promise = new this.constructor(INTERNAL);
if (this.state !== PENDING) {
var resolver = this.state === FULFILLED ? onFulfilled : onRejected;
unwrap(promise, resolver, this.value);
} else {
this.queue.push(new QueueItem(promise, onFulfilled, onRejected));
}
return promise;
}; Promise.prototype.catch = function (onRejected) {
return this.then(null, onRejected);
};
上述代码中的 return this 实现了值穿透,后面会讲。可以看出,then 方法中生成了一个新的 promise 然后返回,符合规范要求。如果 promise 的状态改变了,则调用 unwrap,否则将生成的 promise 加入到当前 promise 的回调队列 queue 里,之前讲解了如何消费 queue。有 3 点需要讲解:
1、Promise 构造函数传入了一个 INTERNAL 即空函数,因为这个新产生的 promise 可以认为是内部的 promise,需要根据外部的 promise 的状态和值产生自身的状态和值,不需要传入回调函数,而外部 Promise 需要传入回调函数决定它的状态和值。所以之前 Promise 的构造函数里做了判断区分外部调用还是内部调用:
if (resolver !== INTERNAL) {
safelyResolveThen(this, resolver);
}
2、unwrap 代码如下:
function unwrap(promise, func, value) {
immediate(function () {
var returnValue;
try {
returnValue = func(value);
} catch (error) {
return doReject(promise, error);
}
if (returnValue === promise) {
doReject(promise, new TypeError('Cannot resolve promise with itself'));
} else {
doResolve(promise, returnValue);
}
});
}
从名字也可以理解是用来解包(即执行函数)的,第一个参数是子 promise,第二个参数是父 promise 的 then 的回调(onFulfilled/onRejected),第三个参数是父 promise 的值(正常值/错误)。
有 3 点需要说明:
2.1、使用 immediate 将同步代码变异步。如:
var Promise = require('appoint')
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('haha')
}, 1000)
})
promise.then(() => {
promise.then(() => {
console.log('1')
})
console.log('2')
})
打印 2 1,去掉 immediate 则打印 1 2。
2.2、try...catch 用来捕获 then/catch 内抛出的异常,并调用 doReject,如:
promise.then(() => {
throw new Error('haha')
})
promise.catch(() => {
throw new Error('haha')
})
2.3、返回的值不能是 promise 本身,否则会造成死循环,如 node@4.6.0 下运行:
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('haha')
}, 1000)
})
var a = promise.then(() => {
return a
}) a.catch(console.log)// [TypeError: Chaining cycle detected for promise #<Promise>]
3、QueueItem 代码如下:
function QueueItem(promise, onFulfilled, onRejected) {
this.promise = promise;
this.callFulfilled = function (value) {
doResolve(this.promise, value);
};
this.callRejected = function (error) {
doReject(this.promise, error);
};
if (isFunction(onFulfilled)) {
this.callFulfilled = function (value) {
unwrap(this.promise, onFulfilled, value);
};
}
if (isFunction(onRejected)) {
this.callRejected = function (error) {
unwrap(this.promise, onRejected, error);
};
}
}
promise 为 then 生成的新 promise(以下称为『子promise』),onFulfilled 和 onRejected 即是 then 参数中的 onFulfilled 和 onRejected。从上面代码可以看出:比如当 promise 状态变为 FULFILLED 时,之前注册的 then 函数,用 callFulfilled 调用 unwrap 进行解包最终得出子 promise 的状态和值,之前注册的 catch 函数,用 callFulfilled 直接调用 doResolve,设置队列里子 promise 的状态和值。当 promise 状态变为 REJECTED 类似。
注意:promise.catch(onRejected) 就是 promise.then(null, onRejected) 的语法糖。
至此,Promise 的核心实现都完成了。
值穿透
Promise.prototype.then = function (onFulfilled, onRejected) {
if (!isFunction(onFulfilled) && this.state === FULFILLED ||
!isFunction(onRejected) && this.state === REJECTED) {
return this;
}
...
};
上面提到了值穿透问题,值穿透即:
var Promise = require('appoint')
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('haha')
}, 1000)
})
promise
.then('hehe')
.then(console.log)
最终打印 haha 而不是 hehe。
通过 return this 只实现了值穿透的一种情况,其实值穿透有两种情况:
1、promise 已经是 FULFILLED/REJECTED 时,通过 return this 实现的值穿透:
var Promise = require('appoint')
var promise = new Promise(function (resolve) {
setTimeout(() => {
resolve('haha')
}, 1000)
})
promise.then(() => {
promise.then().then((res) => {// ①
console.log(res)// haha
})
promise.catch().then((res) => {// ②
console.log(res)// haha
})
console.log(promise.then() === promise.catch())// true
console.log(promise.then(1) === promise.catch({ name: 'nswbmw' }))// true
})
上述代码①②处 promise 已经是 FULFILLED 了符合条件所以执行了 return this。注意:原生的 Promise 实现里并不是这样实现的,所以会打印两个 false。
2、promise 是 PENDING 时,通过生成新的 promise 加入到父 promise 的 queue,父 promise 有值时调用 callFulfilled->doResolve 或 callRejected->doReject(因为 then/catch 传入的参数不是函数)设置子 promise 的状态和值为父 promise 的状态和值。如:
var Promise = require('appoint')
var promise = new Promise((resolve) => {
setTimeout(() => {
resolve('haha')
}, 1000)
})
var a = promise.then()
a.then((res) => {
console.log(res)// haha
})
var b = promise.catch()
b.then((res) => {
console.log(res)// haha
})
console.log(a === b)// false
Promise.resolve 和 Promise.reject
Promise.resolve = resolve;
function resolve(value) {
if (value instanceof this) {
return value;
}
return doResolve(new this(INTERNAL), value);
} Promise.reject = reject;
function reject(reason) {
var promise = new this(INTERNAL);
return doReject(promise, reason);
}
当 Promise.resolve 参数是一个 promise 时,直接返回该值。
Promise.all
Promise.all = all;
function all(iterable) {
var self = this;
if (!isArray(iterable)) {
return this.reject(new TypeError('must be an array'));
} var len = iterable.length;
var called = false;
if (!len) {
return this.resolve([]);
} var values = new Array(len);
var resolved = 0;
var i = -1;
var promise = new this(INTERNAL); while (++i < len) {
allResolver(iterable[i], i);
}
return promise;
function allResolver(value, i) {
self.resolve(value).then(resolveFromAll, function (error) {
if (!called) {
called = true;
doReject(promise, error);
}
});
function resolveFromAll(outValue) {
values[i] = outValue;
if (++resolved === len && !called) {
called = true;
doResolve(promise, values);
}
}
}
}
Promise.all 用来并行执行多个 promise/值,当所有 promise/值执行完毕后或有一个发生错误时返回。可以看出:
1、Promise.all 内部生成了一个新的 promise 返回。
2、called 用来控制即使有多个 promise reject 也只有第一个生效。
3、values 用来存储结果。
4、当最后一个 promise 得出结果后,使用 doResolve(promise, values) 设置 promise 的 state 为 FULFILLED,value 为结果数组 values。
Promise.race
Promise.race = race;
function race(iterable) {
var self = this;
if (!isArray(iterable)) {
return this.reject(new TypeError('must be an array'));
} var len = iterable.length;
var called = false;
if (!len) {
return this.resolve([]);
} var i = -1;
var promise = new this(INTERNAL); while (++i < len) {
resolver(iterable[i]);
}
return promise;
function resolver(value) {
self.resolve(value).then(function (response) {
if (!called) {
called = true;
doResolve(promise, response);
}
}, function (error) {
if (!called) {
called = true;
doReject(promise, error);
}
});
}
}
至此,Promise 的实现全部讲解完毕。
Google大牛分享的面试秘籍的更多相关文章
- Steve Yegge:Google面试秘籍
我憋了很长时间想写点关于去Google面试的秘籍.不过我总是推迟,因为写出来的东西会让你抓狂.很可能是这样.如果按统计规律来定义"你"的话,这文章很可能让你不爽. 为啥呢?因为啊- ...
- 中高级Android大厂面试秘籍,为你保驾护航金三银四,直通大厂(上)
前言 当下,正面临着近几年来的最严重的互联网寒冬,听得最多的一句话便是:相见于江湖~.缩减HC.裁员不绝于耳,大家都是人心惶惶,年前如此,年后想必肯定又是一场更为惨烈的江湖厮杀.但博主始终相信,寒冬之 ...
- 51Testing丛书新作《软件测试工程师面试秘籍》
51Testing又有好消息告诉小伙伴了!51Testing软件测试网作品系列重磅推出全新丛书<软件测试工程师面试秘籍> 此次我们邀请到知名互联网企业测试专家李江(G.li),整理并撰写软 ...
- 转:google测试分享-GTA
原文: http://blog.sina.com.cn/s/blog_6cf812be0102viuh.html 上一次分享了google测试分享-分层测试,有很多自动化测试的策略和实施都要有一个重点 ...
- 如何进BAT,有了这个篇面试秘籍,成功率高达80%!!(附资料)
多年前自己刚来北京找工作的时候,面了一个星期 面了七八家公司才拿到一个offer.而上次跳槽面了不到10家公司基本全过而且都给到了期望的薪资,本来自己在面试前没想到能够这么顺利,回想起来还是自己准备的 ...
- Google测试分享-测试经理
首先大家可以思考下,google的测试经理角色必须具备什么样的能力.据不完全统计,google的测试经理,超过一半之前都做过TE的角色(大家可以想想为啥):对于与被测产品相关的任何使用问题,测试经理都 ...
- 想成为一个高效的Web开发者吗?来看看大牛分享的经验吧~ #精选JAVASCRIPT前端开发
想成为一个高效的Web开发者吗?来看看大牛分享的经验吧~ 作为一个软(ku)件(bi)工(de)程(ma)师(nong),你有没有觉得做什么事都没时间?没时间学习新东西,没时间去回顾.整理原来写的烂代 ...
- 转:google测试分享-问题和挑战
原文: http://blog.sina.com.cn/s/blog_6cf812be0102vxer.html 前言:这个系列分享的内容大部分都是出自于<google是如何测试的>的书, ...
- 转:google测试分享-测试经理
原文: http://blog.sina.com.cn/s/blog_6cf812be0102vode.html 前言:这个系列分享的内容大部分都是出自于<google是如何测试的>的书, ...
随机推荐
- Discuz上传错误
换了服务器后,上传图片的时候,显示上传100%,然后报错:upload error: 500.怎么回事那? [解决方法] 原来是php上传文件的时候,会首先上传到一个临时目录.如果临时目录没有权限,就 ...
- Discuz 学习笔记一 :getgdc 和get_client_ip
Getgdc函数 discuz有一个超级变量的自定义函数: function getgpc($k, $type='GP') { $type = strtoupper($type); ...
- 【PHP 】 伪静态 - 3. 伪静态的基本使用
原理图: 原先浏览器输入的网址会发送到apache服务器,然后apache会调用php模块来处理,最后找到你所想访问的页面; 如果在apahce, httpd.conf文件中开启rewrite机制,则 ...
- Appium Python 四:怎样获取APP的Package以及Activity
看到一篇很好的博客:[Android测试][随笔]获得App的包名和启动页Activity 除了博客上的方法,我还找到两种方法: 方法一:aapt 前提需要使用SDK Manager.exe 下载 A ...
- 带"叉叉"的GridView
由于需要用到“删除图片”的功能,需要写这样一个小demo: 对之前博文的修改 发现imageView监听点击事件 效果实在不敢恭维,因此换个方式:设置Touch的监听函数, 下面的Demo没有改过来哈 ...
- php之快速入门学习-4(数据类型)
PHP 5 数据类型 String(字符串), Integer(整型), Float(浮点型), Boolean(布尔型), Array(数组), Object(对象), NULL(空值). PHP ...
- VB中如何修改treeview的背景色
改变 TreeView 的背景 Private Declare Function SendMessage Lib "User32" Alias "SendMessa ...
- POSIX 线程详解
一种支持内存共享的简捷工具 POSIX(可移植操作系统接口)线程是提高代码响应和性能的有力手段.在本系列中,Daniel Robbins 向您精确地展示在编程中如何使用线程.其中还涉及大量幕后细节,读 ...
- JVM:垃圾回收机制和调优手段
转载请注明出处: jiq•钦's technical Blog - 季义钦 引言: 我们都知道JVM内存由几个部分组成:堆.方法区.栈.程序计数器.本地方法栈 JVM垃圾回收仅针对公共内存区域即:堆和 ...
- 纯jascript解决手机端拍照、选图后图片被旋转问题
需要的js1 需要的js2 这里主要用到Orientation属性. Orientation属性说明如下: 旋转角度 参数 0° 1 顺时针90° 6 逆时针90° 8 180° 3 <!DOC ...