写点什么

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

  • 2022 年 9 月 26 日
    广东
  • 本文字数:3086 字

    阅读完需:约 10 分钟

一个遵循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
复制代码


发布于: 刚刚阅读数: 3
用户头像

公众号:品味前端 2022.09.22 加入

一介前端,卖码为生。很惭愧,只希望在学习和分享的道路上能做一点微小的贡献。

评论

发布
暂无评论
从零开始 - 40行代码实现一个简单Promise函数_JavaScript_茶无味的一天_InfoQ写作社区