如何手写一个Promise

简介: 手写实现Promise是一道经典的前端面试题。比如在美团,Promise几乎就是必考题,在其它互联网一线大厂也是高频题目。Promise的实现相对比较复杂,考虑的逻辑也比较多,下面带着大家逐一分析,找到其关键点、以及如何手写一个Promise。

前言



手写实现Promise是一道经典的前端面试题。比如在美团,Promise几乎就是必考题,在其它互联网一线大厂也是高频题目。


Promise的实现相对比较复杂,考虑的逻辑也比较多,下面带着大家逐一分析,找到其关键点、以及如何手写一个Promise。


(本文实现遵循 promise/A+规范)


1. Promise 基本结构



我们知道实例化Promise对象时传入一个函数作为执行器,有两个参数(resolve和reject)分别将结果变为成功态和失败态。


据此我们可以写出基本结构:

  function Promise(excutor){
            let self = this;
            self.status = 'pending';    // 状态 等待 => 成功或失败
            self.value = null;          // 成功结果
            self.reason = null;         // 失败原因
            function resolve(value){
            };
            function reject(reason){
            };
   }


2. then方法


每一个Promise实例都有一个then方法,它用来处理异步返回的结果,它是定义在原型上的方法。

// 一个成功 一个失败
Promise.prototype.then = function(onFulfilled, onRejected){
}

当我们自己实例化一个Promise时,其执行器函数(executor)会立即执行,这是一定的:

 let demo = new Promise((resolve, reject)=>{ 
                console.log("在座的各位彭于晏,大家好");
            })

因此,当实例化Promise时,构造函数中就要马上调用传入的excutor函数执行,为了防止出错,加入try,catch:

        // 此段代码放在Promise方法里
        try {
                excutor(resolve, reject);
            } catch(err) {
                reject(err)
            }


3. 状态管理



已经是成功态或是失败态不可再更新状态,Promise 规范中规定:当Promise对象已经由pending状态改变为了成功态(resolved)或是失败态(rejected)就不能再次更改状态。


因此我们在更新状态时要判断,如果当前状态是pending(等待态)才可更新。

function resolve(value){
         if (self.status === 'pending') {
                 self.value = value; //保存成功结果
                 self.status = 'fulfilled';
          }
 }
 function reject(reason){
          if (self.status === 'pending') {
                  self.reason = reason;
                  self.status = 'rejected';
           }
}

 以上可以看到:


 1. 在resolve和reject函数中分别加入了判断


 2. 只有当前状态是pending才可进行操作


 3. 将成功的结果和失败的原因都保存到对应的属性上。之后将state属性置为更新后的状态


4. then方法完善



当Promise的状态发生了改变,不论是成功或是失败都会调用then方法。所以,then方法的实现也很简单,根据state状态来调用不同的回调函数即可。

 Promise.prototype.then = function(onFulfilled, onRejected){
            onFulfilled = typeof onFulfilled === 'function' ? 
            onFulfilled : function (data) { resolve(data) }
            onRejected = typeof onRejected === 'function' ? 
            onRejected : function (err) { throw err }
 }

代码写到这里似乎基本功能都实现了,可是还有一个很大的问题:目前此Promise还不支持异步代码,如果Promise中封装的是异步操作,then方法无能为力,例:

 let demo = new Promise((resolve, reject)=>{ 
                console.log("在座的各位彭于晏大家好");
                setTimeout(()=>{
                    resolve(1);
                }, 500)
            })
        // 没有打印
        demo.then(data => console.log(data)); 

第一步是正常打印的,但是 setTimeout 里面的代码不会执行,最后一行也不会打印 “1”。


问题所在:原因是 setTimeout 函数使得 resolve 是异步执行的,有延迟,当调用then方法的时候,此时此刻的状态还是等待态(pending),因此then方法即没有调用 onFulfilled 也没有调用 onRejected。


5. 发布订阅模式登场



这个问题如何解决?我们可以参照发布订阅模式,在执行 then 方法时如果还在等待态(pending),就把回调函数临时寄存到一个数组里,当状态发生改变时依次从数组中取出执行就好,是不是很Nice!


清楚这个思路我们开始实现它,首先在类上新增两个Array类型的数组,用于存放回调函数:

function Promise(excutor){
    // 其它代码略
     self.onFulfilledCallbacks = [];
     self.onRejectedCallbacks = [];
}

在then方法里添加进去:

Promise.prototype.then = function(onFulfilled, onRejected){
           // 其它代码略
            let self = this;
            if(self.status === "pending"){
                 self.onFulfilledCallbacks.push(onFulfilled);
                 self.onRejectedCallbacks.push(onRejected);
            }
}

在resolve() 与reject() 里逐一遍历:

function resolve(value){
    if (self.status === 'pending') {
        // 其它代码略
        self.onFulfilledCallbacks.forEach(item => item(value));
    }
}
function reject(reason){
    if (self.status === 'pending') {
        // 其它代码略
        self.onRejectedCallbacks.forEach(item => item(reason));
    }
}

到这一步,异步也搞定了!再是如何实现链式调用。


6. 经典的链式调用实现



then方法接收的两个函数中,可以通过return把值传给下一个步,也可以返回一个新的Promise把值传给下一步,then方法执行的时候有个特点:


为了保证链式调用,上一次then中不管你是成功还是失败都会把参数作为下一个then中成功时回调的参数


话不多说,开干(只需要再new一个Promise即可):

 Promise.prototype.then = function(onFulfilled, onRejected){
             onFulfilled = typeof onFulfilled === 'function' ? 
             onFulfilled : function (data) { resolve(data) }
             onRejected = typeof onRejected === 'function' ? 
             onRejected : function (err) { throw err }
             let self = this;
            if(self.status === "fulfilled"){
                return new Promise((resolve, reject) => {
                    try{
                        let x = onFulfilled(self.value);
                        x instanceof Promise ? x.then(resolve, reject) : resolve(x);
                    } catch (err){
                        reject(err)
                    }
                })
            }
            if(self.status === "rejected"){
                return new Promise((resolve, reject) => {
                    try{
                        let x = onRejected(self.reason);
                        x instanceof Promise ? x.then(resolve, reject) : resolve(x);
                    } catch (err){
                        reject(err)
                    }
                })
            }
            if(self.status === "pending"){
                return new Promise((resolve, reject) => {
                    self.onFulfilledCallbacks.push(()=>{
                        let x = onFulfilled(self.value);
                        x instanceof Promise ? x.then(resolve, reject) : resolve(x);
                    })
                    self.onRejectedCallbacks.push(()=>{
                        let x = onRejected(self.reason);
                        x instanceof Promise ? x.then(resolve, reject) : resolve(x);
                    })
                })
            }
        }
 }


7. 最后的catch方法



其实catch方法就是then方法的简写

Promise.prototype.catch = function(fn){
            return this.then(null, fn);
        }

现在,一个完整的Promise就实现了,大家可以去试试。有啥不对的可以在评论区指出,感谢。

考虑到一些新手基础可能差点,本篇文章可以说讲解的非常非常详细了,拆分了多个小步骤,一步一步来。相信大家跟着思路走都是能搞定的。排版啥的也都尽量搞得好看些,提高各位客官的用户体验。








目录
相关文章
|
1月前
|
前端开发 JavaScript
在JavaScript中,什么是promise、怎么使用promise、怎么手写promise
在JavaScript中,什么是promise、怎么使用promise、怎么手写promise
25 4
|
5月前
|
前端开发
手写基础 promise
手写基础 promise
41 0
|
6月前
|
存储 前端开发
|
6月前
|
前端开发
Promise的用法&原理&手写实现-2
Promise的用法&原理&手写实现-2
20 1
|
3月前
|
存储 前端开发 JavaScript
面试官:请手写一个Promise
面试官:请手写一个Promise
|
4月前
|
前端开发 JavaScript
手写promise
手写promise
26 1
|
6月前
|
前端开发 JavaScript API
Promise的用法&原理&手写实现-1
Promise的用法&原理&手写实现-1
22 0
|
9月前
|
前端开发
手写Promise
手写Promise
|
9月前
|
前端开发 vr&ar
自定义手写Promise
手写函数版和类版Promise
38 0
|
9月前
|
前端开发
Promise是什么?怎么用?
Promise 是一种异步编程的解决方案。它可以使异步操作更加清晰、简单、优雅,避免了回调地狱的问题。Promise 对象表示一个异步操作的最终完成或失败,并且它的最终状态(完成或失败)和返回值(或错误)不依赖于调用它的代码。