面试高频点-Promise解析和实现
栏目: JavaScript · 发布时间: 6年前
内容简介:Promise因为它的调用方式使得异步操作清晰简单,是现在异步操作的主要方式。Promise的使用和实现是面试中的高频问点。这篇文章主要解析Promise规范和一版实现方式。了解Promise首先我们要清楚Promise规范的内容,规范规定了Promise的行为和调用方式。这里是规范原文。下面是翻译总结:一个Promise主要代表了一个异步操作的最终结果。与Promise交互的主要方式是通过promise实例的
Promise因为它的调用方式使得异步操作清晰简单,是现在异步操作的主要方式。Promise的使用和实现是面试中的高频问点。这篇文章主要解析Promise规范和一版实现方式。
PromiseA+规范
了解Promise首先我们要清楚Promise规范的内容,规范规定了Promise的行为和调用方式。这里是规范原文。下面是翻译总结:
一个Promise主要代表了一个异步操作的最终结果。与Promise交互的主要方式是通过promise实例的 then
方法注册回调函数。回调函数分为两种,一种接受异步操作成功时的回调,接受异步操作结果值作为参数;第二种是异步操作失败时的回调,接受异步操作失败原因为参数。规范主要是详细描述了 then
方法的实现细节。
主要要求如下:
状态
一个promise必须处于这3种状态之中: pending
、 fullfilled
、 rejected
-
pending
: 可以转换到fullfilled
或者rejected
状态 -
fullfilled
: 不能转换到任何状态且有一个不可变的结果值 -
rejected
: 不能转换到任何状态且有一个不可变的失败原因
then
方法
一个 promise
必须有一个 then
方法来接受异步操作的结果值或者失败原因
一个 promise
的then应该接收两个参数,成功回调函数和失败回调函数:
promise.then(onFulfilled, onRejected) 复制代码
-
onFulfilled和onRejected都是可选的:
- 如果onFulfilled不是一个函数,它必须被忽略
- 如果onRejected不是一个函数,它必须被忽略
-
如果onFulfilled是函数:
-
它必须在
promise
状态变为fullfilled
之后执行,且接受promise
的结果值作为第一个参数 -
它在
promise
状态变为fullfilled
之前不能执行 - 它最多被执行一次
-
它必须在
-
如果onRejected是函数:
-
它必须在
promise
状态变为rejected
之后执行,且接受promise
的失败原因作为第一个参数 -
它在
promise
状态变为rejected
之前不能执行 - 它最多被执行一次
-
它必须在
-
onFulfilled
或者onRejected
直到执行环境堆栈只包含平台代码时才可以执行 -
onFulfilled
或者onRejected
必须作为一个独立的函数被调用。(不能作为对象属性执行或者其他指定this的执行方式,比如call和apply) -
then
方法可以在同一个promise
上多次调用:-
当
promise
状态变为fullfilled
时,通过then注册的所有onFulfilled
回调按照注册顺序依次执行 -
当
promise
状态变为rejected
时,通过then注册的所有onRejected
回调按照注册顺序依次执行
-
当
-
then
方法必须返回一个promise:promise2 = promise1.then(onFulfilled, onRejected); 复制代码
-
如果
onFulfilled
或者onRejected
返回x
, 则运行Pomise Resolution Procedure[[Resolve]](promise2, x)
-
如果
onFulfilled
或者onRejected
抛出一个异常e
,promise2
必须以e
作为失败原因变成rejected
状态 -
如果
onFulfilled
不是一个函数,且promise1
为fullfilled
状态,则promise2
也转变为fullfilled
,且结果值和promise1
一样 -
如果
onRejected
不是一个函数,且promise1
为rejected
状态,则promise2
也转变为rejected
,且失败原因和promise1
一样
-
如果
The Promise Resolution Procedure
promise resolution procedure
表示为 [[Resolve]](promise, x)
,是一个接受一个promise和一个值作为参数的抽象操作。如果一个函数是一个 thenable
( thenable
表示有 then
方法的对象或者函数),则它试图使 promise
采用 x
的状态。这里对于 thenable
的处理使Promise更加通用,能够兼容之前并不符合规范但是有合理 then
方法的异步实现。
为了运行 [[Resolve]](promise, x)
,需要执行以下步骤:
-
如果
promise
和x
指向同一个对象,则以TypeError
为失败原因将promise
转变为rejected
状态 -
如果
x
是一个promise,则promise
采用x
的状态以及相应的结果值或者原因 -
如果
x
是一个对象或者函数-
Let
定义then
,值为x.then
-
如果在获取
x.then
的过程中抛出异常e
,promise
以e
作为失败原因变成rejected
状态 -
如果
then
是一个函数,以x
作为它的this
调用它,第一个参数为resolvePromise
,第二个参数为rejectPromise
:-
如果
resolvePromise
以参数y
被调用的话,则执行[[Resolve]](promise, y)
-
如果
rejectPromise
以参数r
被调用的话,则以r
作为失败原因使promise
变成rejected
状态 -
如果
resolvePromise
和rejectPromise
都被调用了,或者以同样的参数被调用多次,则以第一次调用为准,忽略之后的调用 -
如果调用
then
时抛出一个异常e
:-
如果
resolvePromise
或rejectPromise
被调用了,则忽略 -
否则,
promise
以e
作为失败原因变成rejected
状态
-
如果
-
如果
-
如果
then
不是一个函数,则promise
以x
为结果值变为fullfilled
状态
-
-
如果
x
不是一个对象或者函数,则promise
以x
为结果值变为fullfilled
状态
Promise实现
我们可以看出标准还是挺复杂的,我们就一步一步,从简到繁地实现Promise。标准中并没有规定Promise如何创建,如何完成状态转换,这些我们可以参考ES6的promise用法:
const promise1 = new Promise((resolve, reject) => { }); promise1.then((value) => {}, (reason)=>{}) 复制代码
promise的状态
Promise以类的方式出现,构造函数接受一个函数 fn
作为参数, fn
有两个参数:
-
resolve
,使promise从pending
状态转换为fullfilled
状态,resolve(value)
的参数value为promise的结果值; -
reject
,使promise从pending
状态转换为rejected
状态,reject(reason)
的参数reason为promise的失败原因。
我们第一版可以先从状态写起:
const PENDING = 'pending'; const FULLFILLED = 'fullfilled'; const REJECTED = 'rejected'; class Promise { constructor(fn) { this.status = PENDING; this.value = undefined; this.reason = undefined; const resolve = (value) => { if(this.status === PENDING){ this.status = FULLFILLED; this.value = value; } } const reject = (reason) => { if(this.status === PENDING){ this.status = REJECTED; this.reason = reason; } } try{ fn(resolve, reject); }catch(e){ reject(e); } } } 复制代码
try...catch...是为了捕获用户自定义函数 fn
的错误,如果 fn
执行出错,则promise会进入 rejected
状态。
then函数的两个函数参数的执行
接下来我们考虑 then
函数, then
函数比较复杂,我们先考虑 then(onFulfilled, onRejected)
函数中两个函数参数的执行问题,把 then
函数的返回值问题放在后面。
then
函数中两个函数参数的执行情况分为3种:
-
promise处于
pending
状态,那在then函数中不执行两个函数参数,等到fn
中的异步操作有结果了,再根据成功或者失败的结果去执行 -
promise处于
fullfilled
状态,则直接执行then
函数的第一个函数参数onFulfilled
-
promise处于
rejected
状态,则直接执行then
函数的第二个函数参数onRejected
class Promise { constructor(fn) { this.status = PENDING; this.value = undefined; this.reason = undefined; this.fullFilledCallbacks = []; // 存储状态转变之前的注册的onFulfilled this.rejectedCallbacks = []; // 存储状态转变之前的注册的onRejected const resolve = (value) => { if(this.status === PENDING){ this.status = FULLFILLED; this.value = value; this.fullFilledCallbacks.forEach(callback => callback(value)); } } const reject = (reason) => { if(this.status === PENDING){ this.status = REJECTED; this.reason = reason; this.rejectedCallbacks.forEach(callback => callback(reason)); } } try{ fn(resolve, reject); }catch(e){ reject(e); } } then(onFulfilled, onRejected){ if(typeof onFullfilled !== 'function'){ onFullfilled = () => {}; } if(typeof onRejected !== 'function'){ onRejected = () => {}; } if (this.status === 'fullfilled'){ onFulfilled(this.value); } else if (this.status === 'rejected'){ onRejected(this.reason); } else { this.fullFilledCallbacks.push(onFulfilled); this.rejectedCallbacks.push(onRejected); } } } 复制代码
以上代码很简单,我们借助 fullFilledCallbacks
和 rejectedCallbacks
两个数组存储在promise状态为发生转变之前通过 then
方法注册的 onFulfilled
和 onRejected
回调。
then的返回值
由PromiseA+规范可以, then
方法必须返回一个promise:
promise2 = promise1.then(onFulfilled, onRejected); 复制代码
且promise2的状态由 onFullfilled
或者 onRejected
的返回值决定,如何进行转换则由 promise resolution procedure
方法来实现。我们先不考虑 promise resolution procedure
,根据规范实现如下:
then(onFulfilled, onRejected){ let _resolve; let _reject; const promise2 = new Promise((resolve, reject) => { _resolve = resolve; _reject = reject; }); // 如果`onFulfilled`不是一个函数,且`promise1`为`fullfilled`状态,则`promise2`也转变为`fullfilled`,且结果值和`promise1`一样 if(typeof onFullfilled !== 'function'){ onFullfilled = (value) => value; } // 如果`onRejected`不是一个函数,且`promise1`为`rejected`状态,则`promise2`也转变为`rejected`,且失败原因和`promise1`一样 if(typeof onRejected !== 'function'){ onRejected = (reason) => throw new Error(reason); } // 如果`onFulfilled`或者`onRejected`返回`x`, 则运行Pomise Resolution Procedure`[[Resolve]](promise2, x)` const excuteFullfilled = () => { const x = onFulfilled(this.value); this.promiseResolution(promise2, x, _resolve, _reject); } const excuteRejected = () => { const x = onRejected(this.reason); this.promiseResolution(promise2, x, _resolve, _reject); } // 如果`onFulfilled`或者`onRejected`抛出一个异常`e`,`promise2`必须以`e`作为失败原因变成`rejected`状态 try{ if (this.status === 'fullfilled'){ excuteFullfilled(); } else if (this.status === 'rejected'){ excuteRejected(); } else { this.fullFilledCallbacks.push(() => { try{ excuteFullfilled(); }catch(e){ _reject(e); } }); this.rejectedCallbacks.push(() => { try{ excuteRejected(); }catch(e){ _reject(e); } }); } }catch(e){ _reject(e); } return promise2; } promiseResolution(){ ... } 复制代码
接下来我们来实现 promiseResolution
函数,如果已经忘了可以去复习一下 promiseResolution
的规范。 promiseResolution
的主要任务是根据 onFulFilled
或者 onRejected
的返回值 x
来决定 promise2
的状态转变。
promiseResolution(promise, x, resolve, reject){ // 如果`resolvePromise`和`rejectPromise`都被调用了,或者以同样的参数被调用多次,则以第一次调用为准,忽略之后的调用 let called = false; // 如果`resolvePromise`以参数`y`被调用的话,则执行`[[Resolve]](promise, y)` const resolvePromise = (y) => { if(!called){ this.promiseResolution(promise, y, resolve, reject); called = true; } } // 如果`rejectPromise`以参数`r`被调用的话,则以`r`作为失败原因使``变成`rejected`状态 const rejectPromise = (r) => { f(!called){ reject(r); called = true; } } if(promise === x){ throw new Error('TypeError'); } // 如果`x`是一个promise,则`promise`采用`x`的状态以及相应的结果值或者原因 if( x instanceof Promise){ x.then(resovle, reject) } if( x && (typeof x === 'object' || typeof x === 'function')){ let then = x.then; if(typeof then === 'function'){ try{ then.call(x, resolvePromise, rejectPromise); }catch(e){ if(!called){ reject(e); } } }else{ // 如果`then`不是一个函数,则`promise`以`x`为结果值变为`fullfilled`状态 resolve(x); } } else { // 如果`x`不是一个对象或者函数,则`promise`以`x`为结果值变为`fullfilled`状态 resolve(x); } } 复制代码
异步问题
规范中有一条时, onFulfilled
或者 onRejected
直到执行环境堆栈只包含平台代码时才可以执行。规范给出的注释是:平台代码是指引擎、执行环境和Promise实现代码,就是说js主栈中不能有其他代码,这就是要求我们异步执行 onFulfilled
或者 onRejected
;我们可以用宏任务(setTimeout 或者 setImmediate)或者微任务(MutationObserver or process.nextTick)机制来实现。
我们知道,ES6的promise的then方法回调异步执行的机制是微任务。在浏览器环境我们可以使用MutationObserver来模拟微任务机制,如果浏览器不支持 MutationObserver
,我们回退到setTimeout使用宏任务实现。
function isNative(fn){ return fn.toString.includes('native code'); } function asynTask(){ if(typeof MutationObserver === 'function' && isNative(MutationObserver)){ return (fn) => { var targetNode = document.createElement('div'); var config = { attributes: true }; // Create an observer instance linked to the callback function var observer = new MutationObserver(fn); // Start observing the target node for configured mutations observer.observe(targetNode, config); targetNode.id = 'anyway'; } } else if(typeof setImmediate === 'function' && isNative(setImmediate)){ return (fn) => {setImmediate(fn)}; } else{ return (fn) => {setTimeout(fn, 0)}; } } class Promise{ registerAsyn: asynExcute() } 复制代码
我们使用异步机制来重写 then
then(onFulfilled, onRejected){ let _resolve; let _reject; const promise2 = new Promise((resolve, reject) => { _resolve = resolve; _reject = reject; }); // 如果`onFulfilled`不是一个函数,且`promise1`为`fullfilled`状态,则`promise2`也转变为`fullfilled`,且结果值和`promise1`一样 if(typeof onFullfilled !== 'function'){ onFullfilled = (value) => value; } // 如果`onRejected`不是一个函数,且`promise1`为`rejected`状态,则`promise2`也转变为`rejected`,且失败原因和`promise1`一样 if(typeof onRejected !== 'function'){ onRejected = (reason) => throw new Error(reason); } // 如果`onFulfilled`或者`onRejected`返回`x`, 则运行Pomise Resolution Procedure`[[Resolve]](promise2, x)` const excuteFullfilled = () => { try{ const x = onFulfilled(this.value); this.promiseResolution(promise2, x, _resolve, _reject); }catch(e){ _reject(e); } } const excuteRejected = () => { try{ const x = onRejected(this.reason); this.promiseResolution(promise2, x, _resolve, _reject); }catch(e){ _reject(e); } } if (this.status === 'fullfilled'){ registerAysn(excuteFullfilled); } else if (this.status === 'rejected'){ registerAysn(excuteRejected); } else { this.fullFilledCallbacks.push(() => { registerAysn(excuteFullfilled); }); this.rejectedCallbacks.push(() => { registerAysn(excuteRejected); }); } return promise2; } 复制代码
目前为止我们实现了Promise的基本功能。
Promise静态方法和其他实例方法
class Promise{ catch(fn) { return this.then((null, fn); } finally(fn) { const P = this.constructor; return this.then( value => P.resolve(fn()).then(() => value), reason => P.resolve(fn()).then(() => { throw reason }) ); } } //resolve方法 Promise.resolve = function(value) { if (value instanceof Promise) { return value; } return new Promise(function(resolve) { resolve(value); }); }; Promise.reject = function(value) { return new Promise(function(resolve, reject) { reject(value); }); }; Promise.race = function(promises) { return new Promise(function(resolve, reject) { for (var i = 0, len = promises.length; i < len; i++) { promises[i].then(resolve, reject); } }); }; Promise.all = function(promises){ if (!promises || typeof promises.length === 'undefined') throw new TypeError('Promise.all should accepts an array'); let values = []; let i = 0; let length = promises.length; function processData(index,data){ values[index] = data; i++; if(i == length){ resolve(arr); }; }; return new Promise((resolve,reject)=>{ for(let i=0;i<promises.length;i++){ promises[i].then(data=>{ processData(i,data); },reject); }; }); } 复制代码
以上所述就是小编给大家介绍的《面试高频点-Promise解析和实现》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Java 面试题 200+ 全解析:面试必备
- 面试必备:《Java 最常见 200+ 面试题全解析》
- Golang面试题解析(五)
- 测试工程师面试解析~
- JVM常见面试题解析
- Golang面试题解析(五) 原
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Google成功的七堂课
罗耀宗 / 电子工业出版社 / 2005-7 / 28.00元
Google是全球使用人数最多的搜索引擎,在短短几年内,Google从斯坦福大学的实验室,茁壮成长为举世瞩目的IT业超级巨人,他们的成功绝非偶然,尤其是在网络泡沫破灭,行业一片萧条之际,它的崛起更为IT业带来一缕曙光。作者从趋势观察家的角度,以讲座的形式,向读者讲述Google成功的关键因素:破除因循守旧、不断打破常规,核心技术领先、做出了“更好的捕鼠器”,使得Google在搜索技术方面远远超越对......一起来看看 《Google成功的七堂课》 这本书的介绍吧!