摘要:内部总体上分为两种情况,一种是当前对象状态已经变为或,此时则直接把响应的回调函数添加到异步队列中,另一种情况是当前对象状态还是,此时则把响应的回调函数依次添加到数组中。
今天,我带着大家一步一步跟着规范实现一个自己的Promise,大家可以对照我的第二篇文章Promise介绍--规范篇或官方规范来一一学习。
Promise内部有三个固定的状态,我们在文件中提前定义。
const PENDING = "pending" const FULFILLED = "fulfilled" const REJECTED = "rejected"
首先,是构造器constructor。
constructor(resolver){ this._status = PENDING; // 保存内部的状态 this._result = undefined; // 保存promise对象fulfill或reject的最终结果 this._childArr = []; // 调用then方法创建的子promise对象 this._fulfillArr = []; // 调用then方法添加的onFulfilled方法 this._rejectArr = []; // 调用then方法添加的onRejected方法 if (resolver == noop) { return}; // then方法内部创建promise时间使用 // 如果resolver不是函数,则抛出TypeError错误 if (!isFunction(resolver)) { throw new TypeError("参数必须为function"); }; // 如果直接调用Promise而非通过new关键词创建,同样抛出TypeError错误 if (this instanceof Promise) { initPromise(this, resolver) } else { throw new TypeError("Promise不可以直接作为函数调用") } }
当参数传递正确时,才真正初始化Promise对象,我提到了一个多带带的函数initPromise中,如下:
function initPromise(promise, resolver){ // 当调用传入的resolver函数抛出异常,则reject当前promise try { resolver(function(value){ // 封装的内部函数,处理resolve(value),也就是Promise Resolution Procedure resolve(promise, value); }, function(reason){ // 封装的内部函数,处理reject(reason) reject(promise, reason); }); } catch (e) { reject(promise, e); } }
当我们执行new Promise((resolve){resolve(5)})时,会走resolve(promise, value)。接下来我们实现一下Promise Resolution Procedure。
function resolve(promise, value){ // 2.3.1 如果promise和value指向同一对象 if (promise === value) { reject(promise, new TypeError("不可以resolve Promise实例本身")) // 2.3.2 如果value是一个promise } else if (value instanceof Promise) { // 2.3.2.2 如果value处于fulfilled状态,则使用相同的value值fulfill promise。 if (value._status == FULFILLED) { fulfill(promise, value._result); // 2.3.2.3 如果value处于rejected状态,则使用相同的reason值reject promise。 } else if (value._status == REJECTED) { reject(promise, value._result); // 2.3.2.1 如果value处于pending状态,则promise同样pending并直到value状态改变。 // 重新把resolve(promise, value)添加到队列,asyncCall封装了一下异步调用 } else { asyncCall(resolve, [promise, value]); } // 2.3.3 如果x是一个object或function } else if (isObjectOrFunction(value)){ // 2.3.3.2 如果获取value.then的值时抛出异常,则通过该异常reject promise try{ let then = value.then; // 2.3.3.1 使then等于value.then // 2.3.3.3 如果then是一个函数 if (isFunction(then)) { try{ handleThenable(promise, value, then); } catch (e) { reject(promise, e); } // 2.3.3.4 如果then不是一个函数 } else { fulfill(promise, value); } } catch (e) { reject(promise, e); } // 2.3.4 value不是对象或函数 } else { fulfill(promise, value); } }
因为value.then是函数时,处理情况同样很多且比较杂乱,我多带带把这部分处理提取到handleThenable函数中。实现如下:
function handleThenable(promise, value, then){ let settled = false; // 是否fulfilled或rejected try { // 2.3.3.3 如果then是一个函数,则把value作为函数中this指向来调用它 then.call(value, (otherValue)=>{ // 2.3.3.3.3 if (settled) { return}; // 2.3.3.3.1 如果resolvePromise通过传入y来调用,则执行resolve(promise, y) resolve(promise, otherValue); settled = true; }, (reason)=>{ // 2.3.3.3.3 if (settled) { return}; // 2.3.3.3.2 如果rejectPromise 通过传入原因r来调用,则传入r来reject promise reject(promise, reason); settled = true; }) // 2.3.3.3.4 如果调用then抛出异常e } catch (e) { // 2.3.3.3.4.1 如果resolvePromise或rejectPromise已经调用,则忽略 if (settled) { return}; settled = true; // 2.3.3.3.4.2 否则,则传入e来reject promise reject(promise, e) } }
以上,基本就是完整的我对Promise Resolution Procedure的实现。
还有一个非常重要的方法就是then方法,接下来我们看一下它是如何实现的。then内部总体上分为两种情况,一种是当前promise对象状态已经变为fulfilled或rejected,此时则直接把响应的回调函数添加到异步队列中,另一种情况是当前promise对象状态还是pending,此时则把响应的回调函数依次添加到数组中。
then(onFulfilled, onRejected){ let child = new Promise(noop); // 如果当前对象状态已经改变,则直接根据状态调用响应的回调 if (this._status !== PENDING) { if (this._status == FULFILLED) { // 2.2.4 异步调用 asyncCall(()=>{ dealThen(this, child, onFulfilled); }) } else { // 2.2.4 异步调用 asyncCall(()=>{ dealThen(this, child, onRejected); }) } // 如果当前对象处于pending状态,则把onFulfilled, onRejected添加到 } else { this._childArr.push(child); this._fulfillArr.push(onFulfilled); this._rejectArr.push(onRejected); } // 返回一个新的promise对象 return child; }
具体处理逻辑我放到了一个新的函数dealThen中,注意它是异步调用的。所以用asyncCall方法包装了一下。
// 处理then function dealThen(promise, child, x){ // onFulfilled或onRejected是一个函数 if (isFunction(x)) { // 2.2.7.1 如果onFulfilled或onRejected返回了一个值value,则执行resolve(child, value) try { resolve(child, x(promise._result)); // 2.2.7.2 如果onFulfilled或onRejected抛出了异常e,则reject child并传入原因e } catch (e) { reject(child, e); } } else { try{ // 2.2.1.1 如果onFulfilled不是一个函数,则忽略 if (promise._status == FULFILLED) { fulfill(child, promise._result); // 2.2.1.2 如果onRejected不是一个函数,则忽略 } else { reject(child, promise._result); } } catch (e) { reject(child, e); } } }
从上面的代码中我们看到有两个比较重要的方法——fulfill和reject,它们才是真正改变promise状态并调用相应回调的地方。
function fulfill(promise, value){ // 如果状态已经不是pending,则直接return if (promise._status !== PENDING) { return }; // 设置状态为fulfilled,并设置最终结果 promise._status = FULFILLED; promise._result = value; // 异步依次调用添加的onFulfilled方法 if (promise._fulfillArr.length > 0) { // 2.2.6.1 如果promise fulfilled,则所有的onFulfilled回调函数按照它们添加的顺序依次调用。 promise._fulfillArr.forEach((k,index)=>{ // 2.2.5 onFulfilled和onRejected必须作为函数来调用,没有this值 asyncCall(dealThen, [promise, promise._childArr[index], k]) }); } } function reject(promise, reason){ // 如果状态已经不是pending,则直接return if (promise._status !== PENDING) { return }; // 设置状态为rejected,并设置最终结果 promise._status = REJECTED; promise._result = reason; // 异步依次调用添加的onRejected方法 if (promise._rejectArr.length > 0) { // 2.2.6.2 如果promise rejected,则所有的onRejected回调函数按照它们添加的顺序依次调用。 promise._rejectArr.forEach((k,index)=>{ // 2.2.5 onFulfilled和onRejected必须作为函数来调用,没有this值 asyncCall(dealThen, [promise, promise._childArr[index], k]) }); } }
当然,还有一个实例方法catch,其实它调用的也是then方法。
catch(onRejected){ return this.then(undefined, onRejected); }
当然,Promise还有四个实例方法,分别如下:
resolve
Promise.resolve = function(value){ return new Promise(function(resolve){resolve(value)}) }
reject
Promise.reject = function(reason){ return new Promise(function(resolve, reject){reject(reason)}) }
all和race的实现没有太好好思考,也没有跑测试,不知道有没有问题,而且个人感觉实现的也比较挫,是用setInterval一直轮询查看每一个promise实例状态有没有改变,所以就不show code了。主要内容还是讲Promises/A+的实现。
完整的代码见我的github。
当然也有几个问题,异步我是用setTimeout实现的,它属于macro-task,而原生的Promise属于micro-task,所以这里还有待改进。
另外,在上面的实现中,我们发现resolve(promise, value)中,在对2.3.2.1 如果value处于pending状态,则promise同样pending并直到value状态改变。我基本采用的也是setTimeout
轮询的方式实现的。之后看了es-promise的实现,因为根据2.3.2.1此时promise的状态和value是同步的,所以可以把resolve和reject promise分别放在value相应状态的回调中,并假设此时与之对应的value的child是undefined。如下所示:
// asyncCall(resolve, [promise, value]); value._childArr.push(undefined); value._fulfillArr.push((value)=>{resolve(promise, value)}); // ① value._rejectArr.push((reason)=>{reject(promise, reason)}); // ②
此时我们还需要对fulfill和reject两个方法稍作改动。
function fulfill(promise, value){ if (promise._status !== PENDING) { return }; promise._status = FULFILLED; promise._result = value; if (promise._fulfillArr.length > 0) { promise._fulfillArr.forEach((k,index)=>{ // 如果对应的child不是undefined,则异步调用回调 if (promise._childArr[index]) { asyncCall(dealThen, [promise, promise._childArr[index], k]) // 如果对应的child是undefined,则直接执行回调函数①并传入value } else { k(value); } }); } } function reject(promise, reason){ if (promise._status !== PENDING) { return }; promise._status = REJECTED; promise._result = reason; if (promise._rejectArr.length > 0) { promise._rejectArr.forEach((k,index)=>{ // 如果对应的child不是undefined,则异步调用回调 if (promise._childArr[index]) { asyncCall(dealThen, [promise, promise._childArr[index], k]) // 如果对应的child是undefined,则直接执行回调函数②并传入reason } else { k(reason); } }); }; }
修改版见Promise1。
错误或不足之处,欢迎指正。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/91309.html
摘要:我们称为回调对象,它内部会维护一个数组,我们可以向其中添加若干个回调函数,然后在某一条件下触发执行。第一次之后,再次新的回调函数时,自动执行回调。当前面的回调函数返回时,终止后面的回调继续执行。 最近懒癌发作,说好的系列文章,写了一半,一直懒得写,今天补上一篇。 Deferred 我们在使用promise对象时,总会提到一个与它关系密切的对象——Deferred。其实Deferred没...
摘要:规范中对于构造函数没有明确说明,所以在此处拿出来讲解一下。构造函数只接收一个参数,且该参数必须是一个函数,任何其他的值比如等都会报一个的错误。 本篇文章是Promise系列文章的第二篇,主要是讲解基于Promise/A+规范,在传入不同类型的参数时,promise内部分别会如何处理。本章的主要目的是让大家对promise有一个更加深入的理解,也为下一篇讲如何实现一个promise库做准...
摘要:请求的传统写法改为后的写法很显然,我们把异步中使用回调函数的场景改为了等函数链式调用的方式。数组中第一个元素是异步的,第二个是非异步,会立即改变状态,所以新对象会立即改变状态并把传递给成功时的回调函数。 前言 Promise,相信每一个前端工程师都或多或少地在项目中都是用过,毕竟它早已不是一个新名词。ES6中已经原生对它加以支持,在caniuse中搜索一下Promise,发现新版的ch...
摘要:的翻译文档由的维护很多人说,阮老师已经有一本关于的书了入门,觉得看看这本书就足够了。前端的异步解决方案之和异步编程模式在前端开发过程中,显得越来越重要。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。 JavaScript Promise 迷你书(中文版) 超详细介绍promise的gitbook,看完再不会promise...... 本书的目的是以目前还在制定中的ECMASc...
摘要:因此,当作为参数的执行任意结果的回调函数时,就会将参数传递给外层的,执行对应的回调函数。 背景 在上一篇博客[[译]前端基础知识储备——Promise/A+规范](https://segmentfault.com/a/11...,我们介绍了Promise/A+规范的具体条目。在本文中,我们来选择了promiz,让大家来看下一个具体的Promise库的内部代码是如何运作的。 promiz...
阅读 2670·2023-04-25 18:10
阅读 1604·2019-08-30 15:53
阅读 2787·2019-08-30 13:10
阅读 3215·2019-08-29 18:40
阅读 1127·2019-08-23 18:31
阅读 1200·2019-08-23 16:49
阅读 3399·2019-08-23 16:07
阅读 876·2019-08-23 15:27