从零开始 - 40行代码实现一个简单Promise函数

简介: 对实现规范中的核心部分进行一次编写练习,目的在于跨苏掌握Promise核心原理。
一个遵循 PromiseA+规范的函数,个人认为解决了callback异步回调地狱的问题,注意是callback方式的回调地狱,promise本身也可以存在回调地狱,需配合ES7特性async、await才能做到完全解决回调地狱。

Promise主要特点

  1. Promise 会有三种状态,「进⾏中」「已完成」和「已拒绝」,进⾏中状态可以更改为已完成或已拒绝,已经更改过状态后⽆法继续更改(例如从已完成改为已拒绝)。
  2. Promise 构造之后需要传⼊⼀个函数,它接受两个参数,执⾏第⼀个参数之后就会改变当前 promise 为「已完成」状态,执⾏ 第⼆个参数之后就会变为「已拒绝」状态。
  3. 通过 .then ⽅法,即可在上⼀个 promise 达到已完成 时继续执⾏下⼀个函数或 promise。同时通过 resolve 或 reject 时传⼊参数,即可给下⼀个函数或 promise 传⼊初始值。
  4. 已拒绝的 promise,后续可以通过 .catch ⽅法或是 .then ⽅法的第⼆个参数或是 try catch 进⾏捕获。

根据特性梳理结构

class simplePromise {
    constructor(handleFn) { // 构造时传入一个函数
        this.status = 'pending' // 有状态控制
        handleFn(resolve, reject) // 该函数接收两个参数
    }
    then() { // 它有一个then方法
        return new simplePromise(() => { // 返回的是promise实例then才得以串联起来
            if (this.status === 'pending') { // 状态一旦改变就不可修改
                // TODO
            }
        })
    }
    // 一些常用的静态方法
    catch() {}
    resolve() {}
    reject() {}
}

实现resolve

这是Promise的第一个关键点,主要在于逻辑的判断,如何将then串联起来,以及最后收集依赖

class SimplePromise {
    constructor(handleFn) {
        this.status = 'pending' // 标记状态
        this.fulfilledList = [] // 任务队列
        handleFn(params => { })
    }
    then(onFulfilled, onRejected) { // then()接收两个函数
        // 返回一个Promise
        return new SimplePromise((onNextFulfilled, onNextRejected) => {
            function finalFn(params) {
                // 如果不是函数,跳过执行下一步
                if (typeof onFulfilled !== 'function') {
                    onNextFulfilled(params)
                } else {
                    // 如果是函数,先接收其返回结果
                    const res = onFulfilled(params)
                    // 判断是否为Promise,也可以判断有没有then方法typeof res.then === 'function'
                    if (res && res instanceof SimplePromise) {
                        res.then(onNextFulfilled) // 继续执行Promise
                    } else {
                        onNextFulfilled(res) // 不是Promise继续执行下一步
                    }
                }
            }
            if (this.status === 'pending') {
                this.fulfilledList.push(finalFn) // 收集依赖
            }
        })
    }
}

第二个关键点在于,用户传入的函数,在构造时会立即执行,但收集依赖是在then方法中,所以需要将传入的函数放到异步队列中去执行

class SimplePromise {
    constructor(handleFn) {
        this.status = 'pending' // 标记状态
        this.fulfilledList = [] // 任务队列
        handleFn((val) => {
            setTimeout(() => { // 抛进异步队列,保证不会立即执行
                if (this.status !== 'pending') { return } // 状态一旦确定再无法变更
                this.status = 'fulfilled' // 执行就改变状态
                this.fulfilledList.forEach(fn => fn(val))// 开始执行函数
                this.fulfilledList = [] // 释放
            }, 0)
        })
    }
    then(onFulfilled, onRejected) { ..... }
    catch() { }
    resolve() { }
    reject() { }
}
  • 接下来将reject也实现进去,handleFn为用户传入的函数,其接收两个参数resolve和reject,我们分别执行了两个队列里收集的任务。
  • 而then中则创建了一个工厂函数,用于生成两种收集依赖的方法。

完整代码

class SimplePromise {
    constructor(handleFn) {
        this.status = 'pending' // 标记状态
        this.fulfilledList = [] // 任务队列
        this.rejectedList = []
        handleFn(this.trigger.bind(this, 'fulfilled'), this.trigger.bind(this, 'rejected'))
    }
    trigger(status, val) {
        setTimeout(() => { // 抛进异步队列,保证不会立即执行
            if (this.status !== 'pending') return; // 状态一旦确定再无法变更
            this.status = status // 状态变更
            this[`${status}List`].forEach(fn => fn(val)) // 开始执行函数
        }, 0)
    }
    then(onFulfilled, onRejected) {
        return new SimplePromise((onNextFulfilled, onNextRejected) => {
            function createFinalFn(prev, next) {
                return function (params) {
                    if (typeof prev !== 'function') {
                        next(params)
                    } else {
                        const res = prev(params)
                        res && res instanceof SimplePromise ? res.then(next) : next(res)
                    }
                }
            }
            if (this.status === 'pending') {
                this.fulfilledList.push(createFinalFn(onFulfilled, onNextFulfilled))
                this.rejectedList.push(createFinalFn(onRejected, onNextRejected))
            }
        })
    }
    catch(onRejected) { // 返回reject
        return this.then(null, onRejected)
    }
    static resolve(val) { // 直接成功执行的结果
        return new SimplePromise(resolve => resolve(val))
    }
    static reject(val) { // 暴露出失败结果,或许可以用来做Promise的中断
        return new SimplePromise((resolve, reject) => reject(val))
    }
}

至此大概40行的代码实现了一个简单的Promise,写个例子测试下

const p = new SimplePromise((resolve, reject) => {
    resolve('success');
}).then(res => {
    console.log('ok', res)
}, err => {
    console.log('no', err)
})
// ok success

注:本例中实现的简单Promise无法跑通全部官方测试用例,只是对实现规范中的核心部分进行一次编写练习。

主要理解以上两个关键点其实就理解了Promise的核心部分,面试中可应对大部分Promise原理考察,如果面试官要你用手写完整的Promise实现,那你应该反问他贵公司是不是从来不使用电脑办公。

最后用上我们手写的SimplePromise来运行一道综合题加深对Promise的认识

var promise = new SimplePromise(function(resolve, reject){
  setTimeout(function() {
    resolve(1);
  }, 1000)
})

promise.then(() => {
  return SimplePromise.resolve(2);
}).then((n) => {
  console.log('我是第一个:结果是',n)
});

promise.then(() => {
  return 2
}).then((n) => {
  console.log('我是第二个:结果是',n)
});

promise.then(2).then((n) => {
  console.log('我是第三个:结果是',n)
});

输出结果:

我是第二个: 结果是2
我是第三个: 结果是1
我是第一个: 结果是2
相关文章
|
5月前
|
前端开发 安全
协程问题之协程函数返回的Promise对象必须满足哪些要求
协程问题之协程函数返回的Promise对象必须满足哪些要求
|
4月前
|
前端开发
手写实现ES6的Promise.all()和Promise.race()函数
这篇文章介绍了如何手写实现ES6的`Promise.all()`和`Promise.race()`函数,提供了实现这两个Promise聚合函数的详细代码示例,并展示了如何使用它们。
手写实现ES6的Promise.all()和Promise.race()函数
|
前端开发 JavaScript API
ES6-ES11-第一部分-let、const、解构赋值、模板字符串、简化对象写法、箭头函数、函数参数默认值、rest 参数、扩展运算符、Symbol、迭代器、生成器、Promise、Set、Map(五)
ES6-ES11-第一部分-let、const、解构赋值、模板字符串、简化对象写法、箭头函数、函数参数默认值、rest 参数、扩展运算符、Symbol、迭代器、生成器、Promise、Set、Map(五)
|
7月前
|
前端开发
箭头函数与promise
箭头函数与promise
47 0
|
设计模式 JSON 前端开发
前端面试必看(手写Promise+js设计模式+继承+函数柯里化等)JavaScript面试全通关(1/3)
前端面试必看(手写Promise+js设计模式+继承+函数柯里化等)JavaScript面试全通关(1/3)
79 0
|
前端开发
前端学习笔记202306学习笔记第四十二天-函数返回promise
前端学习笔记202306学习笔记第四十二天-函数返回promise
46 0
|
前端开发 API 网络架构
ES6-ES11-第一部分-let、const、解构赋值、模板字符串、简化对象写法、箭头函数、函数参数默认值、rest 参数、扩展运算符、Symbol、迭代器、生成器、Promise、Set、Map(六)
ES6-ES11-第一部分-let、const、解构赋值、模板字符串、简化对象写法、箭头函数、函数参数默认值、rest 参数、扩展运算符、Symbol、迭代器、生成器、Promise、Set、Map(六)
|
前端开发 网络架构
ES6-ES11-第一部分-let、const、解构赋值、模板字符串、简化对象写法、箭头函数、函数参数默认值、rest 参数、扩展运算符、Symbol、迭代器、生成器、Promise、Set、Map(四)
ES6-ES11-第一部分-let、const、解构赋值、模板字符串、简化对象写法、箭头函数、函数参数默认值、rest 参数、扩展运算符、Symbol、迭代器、生成器、Promise、Set、Map(四)
|
前端开发 网络架构
ES6-ES11-第一部分-let、const、解构赋值、模板字符串、简化对象写法、箭头函数、函数参数默认值、rest 参数、扩展运算符、Symbol、迭代器、生成器、Promise、Set、Map(二)
ES6-ES11-第一部分-let、const、解构赋值、模板字符串、简化对象写法、箭头函数、函数参数默认值、rest 参数、扩展运算符、Symbol、迭代器、生成器、Promise、Set、Map(二)
|
前端开发 网络架构
ES6-ES11-第一部分-let、const、解构赋值、模板字符串、简化对象写法、箭头函数、函数参数默认值、rest 参数、扩展运算符、Symbol、迭代器、生成器、Promise、Set、Map(一)
ES6-ES11-第一部分-let、const、解构赋值、模板字符串、简化对象写法、箭头函数、函数参数默认值、rest 参数、扩展运算符、Symbol、迭代器、生成器、Promise、Set、Map