Promise从手写到扩展 | Promise/Generator/async | [Promise系列二](一)

简介: Promise从手写到扩展 | Promise/Generator/async | [Promise系列二](一)

前情提要


Promise从入门到手写:juejin.cn/post/693968…


上一篇文章主要分为四个部分:


  • Promise介绍
  • Promise特点
  • Promise使用
  • Promise手写


如果有需要得可以点击上面的链接,原本这篇文章我个人认为是比较完整的,但是经过同事,以及掘金评论区的小伙伴的反馈,我决定再对之前的文章进行一些补全。


本篇文章内容如下:


  • Promise补充:包括Promise A+规范 | 用类的方式对Promise进行手写
  • Generator: Generator介绍与用法 | Generator与协程 | Generate自动执行(执行器)
  • async/await:async/await用法 | 对比Generator的改进项
  • 知识补充:Thunk函数 | co模块介绍与源码
  • 私货:结束语 | 属于我风格的经典文章结束闲扯淡

ok,那我们正式开始我们的旅程吧~


Promise补充


Promise A+规范


在前文我们已经实现过Promise了,后面我们还会用类的方式再写一次,但是我貌似并没有去介绍Promise A+规范官网


大家其实只需要去关注上面链接的第二章。第二章分为三个部分:


  • Promise States 即 Promise的状态
  • The then Method 即 then方法
  • The Promise Resolution Procedure 即 Promise的处理过程


在这里我搜索到了一个Promise A+规范中英文对照翻译我也在这里做一个简单总结:


  1. Promise本质是一个状态机,且状态只能为以下三种:Pending(等待)、Fulfilled(成功)、Rejected(失败),状态的变更是单向且不可逆的,只能从Pending -> FulfilledPending -> Rejected
  2. then方法接收两个可选参数,分别对应状态改变时触发的回调。then方法返回一个promisethen 方法可以被同一个 promise 调用多次。


当然,其实Promise A+规范还有很多细节,我的总结其实很不完整,比如官网的第二章第三节,Promise处理过程我很难去简单总结,而且上面那个中英文对照翻译的很好,大家有时间建议看一看,其实网络上很多关于Promise A+规范的介绍,也其实像我这般简单说说,具体细节还是要去看官网或者翻译中英文对照翻译的~


而且其实按照完整的Promise A+规范,其实之前的代码在一些细节上不能完全通过Promise A+规范。


Promise手写(类的方式)


变动和之前文章变动不大。但是有一点要注意,像resolve,reject,all,race是类的静态方法,所以要加上static关键字。


关于各个方法的写法,思路,在上一篇文章有完整的介绍,如果有不懂的,可以去看看之前的文章。相信可以解答大部分问题。


//Promise类的写法
class Promise {
    // 构造方法
    constructor(executor) {
        //保存promise状态
        this.promiseState = 'pending';
        //保存promise结果
        this.promiseResult = null;
        //用于保存异步回调函数列表
        this.callbackList = [];
        const resolve = val => {
            // 状态只能修改一次
            if (this.promiseState !== 'pending') return;
            // 1. 要去修改Promise对象的状态([[promiseState]]),置为成功fulfilled
            this.promiseState = 'fulfilled';
            // 2. 要去修改Promise对象的状态([[promiseResult]])
            this.promiseResult = val;
            setTimeout(() => {
                // 调用成功的回调【callbackList存起来的】
                for (let callback of this.callbackList) {
                    callback.onResolved(val);
                }
            })
        }
        const reject = err => {
            // 状态只能修改一次
            if (this.promiseState !== 'pending') return;
            // 1. 要去修改Promise对象的状态([[promiseState]]),置为失败rejected
            this.promiseState = 'rejected';
            // 2. 要去修改Promise对象的状态([[promiseResult]])
            this.promiseResult = err;
            setTimeout(() => {
                // 调用失败的回调【callbackList存起来的】
                for (let callback of this.callbackList) {
                    callback.onRejected(err);
                }
            })
        }
        // 为什么要加try catch 是因为,throw err也相当于调用reject了【前面说过没看过的去补课】
        try {
            /*
            * 同步执行执行器函数
            * 执行器函数接收两个参数,一个是resolve,一个是reject
            */
            executor(resolve, reject);
        } catch (err) {
            reject(err);
        }
    }
    // then方法
    then(onResolved, onRejected) {
        const self = this;
        //处理异常穿透 并且为onResolved,onRejected设置默认值。因为这两个参数可以都不传
        if (typeof onRejected !== 'function') {
            onRejected = err => {
                throw err;
            }
        }
        if (typeof onResolved !== 'function') {
            onResolved = val => val;
        }
        // then方法会返回Promise
        return new Promise((resolve, reject) => {
            // 对返回值的处理进行封装
            const handleCallback = (callback) => {
                // 如果回调函数中抛出错误,则reject
                try {
                    // 需要依据回调的返回结果确定then方法的返回值
                    // 现在的this会指向return的promise对象,所以使用self
                    const res = callback(self.promiseResult);
                    if (res instanceof Promise) {
                        //如果回调返回结果是个Promise
                        res.then(val => {
                            resolve(val);
                        }, err => {
                            reject(err);
                        })
                    } else {
                        // 返回结果不是Promise
                        resolve(res);
                    }
                } catch (err) {
                    reject(err);
                }
            }
            //调用回调函数
            if (this.promiseState === 'fulfilled') {
                setTimeout(() => {
                    handleCallback(onResolved);
                })
            }
            if (this.promiseState === 'rejected') {
                setTimeout(() => {
                    handleCallback(onRejected);
                })
            }
            /*
            * 如果是pending状态,则异步任务,在改变状态的时候去调用回调函数
            * 所以要保存回调函数
            * 因为promise实例阔以指定多个回调,于是采用数组 
            */
            if (this.promiseState === 'pending') {
                this.callbackList.push({
                    onResolved: () => {
                        handleCallback(onResolved);
                    },
                    onRejected: () => {
                        handleCallback(onRejected);
                    }
                })
            }
        })
    }
    // catch方法
    catch(onRejected) {
        //  我们可以直接使用then方法实现
        return this.then(undefined, onRejected);
    }
    // resolve方法
    static resolve(val) {
        //返回值的情况在前文说过,可以在 Promise的使用一章找到
        return new Promise((resolve, reject) => {
            if (val instanceof Promise) {
                val.then(val => {
                    resolve(val);
                }, err => {
                    reject(err);
                });
            } else {
                resolve(value);
            }
        })
    }
    // reject方法
    static reject(err) {
        //返回值的情况在前文说过,可以在 Promise的使用一章找到
        return new Promise((resolve, reject) => {
            reject(err);
        })
    }
    // all方法
    static all(promiseList) {
        let count = 0;
        let res = [];
        const length = promiseList.length;
        return new Promise((resolve, reject) => {
            for (let i = 0; i < length; i++) {
                promiseList[i].then(val => {
                    count++;
                    res[i] = val;
                    if (count === length) {
                        resolve(res);
                    }
                }, err => {
                    reject(err);
                });
            }
        })
    }
    // race方法
    static race(promiseList) {
        const length = promiseList.length;
        //谁先完成谁就决定结果!
        return new Promise((resolve, reject) => {
            for (let i = 0; i < length; i++) {
                promiseList[i].then(val => {
                    resolve(val);
                }, err => {
                    reject(err);
                });
            }
        })
    }
}


Generator


引子


其实我们写多了Promise,其实我们也会发现一个问题,Promise虽然解决了回调地狱的问题,但是我们作为开发者,比如我这种有着半年开发经验的资深前端工程师(手动狗头),其实也遇到了很多比较复杂的情况,导致代码中有很多难读的then方法引起的嵌套结构。


吐槽:当然,虽然很多导致难于理解的原因是历史因素或者开发者的随意导致的。当时从结果出发,我还是不太喜欢会出现嵌套的Promise.prototype.then()的异步处理方式。


但是我们还是期望一种技术可以解决then导致的嵌套结构。把异步代码像同步代码一样处理,于是我在开发中最常使用的async/await在ES7引入了,async/await提供了以同步代码实现异步任务的能力,使得写异步代码像同步代码一样清晰。


但是,在我们研究async/await之前,我们需要去研究一下Generator,因为asyncGenerator的语法糖。


Generator简单介绍


网上关于Generator的用法有很多,大家可以看更详细的文档,我不会在此详细介绍用法~抱歉啦


GeneratorECMAScript 2015ES6提出的规范。


首先遇事不决,先说用法:


function* 这种声明方式(function关键字后跟一个星号)会定义一个生成器函数 (generator function),它返回一个  Generator  对象。


我们直接对MDN上面的例子加以修改:


function* generator(i) {
  console.log('第一次')
  yield i;
  console.log('第二次')
  yield i + 10;
  console.log('第三次')
}
const c = generator(1);


为了比较清晰,我直接把执行过程的打印结果截图:


网络异常,图片无法展示
|


我们仔细观察这个图片,我们不难发现几件事情:


  • 首先就是function* 这种声明方式定义的生成器函数返回一个对象。这个对象我们直观的发现里面有一个叫做[[GeneratorState]]的属性,里面代表着这个生成器对象的状态。
  • Generator.prototype.next()方法会返回一个由 yield表达式生成的值,并使得函数执行到该yield的位置并暂停。该值是一个对象,有valuedone两个属性,value是返回结果,即yield表达式后面那个表达式的值,done代表生成器对象是否已经油尽灯枯,即生成器对象是否遍历结束。[伙伴们,油尽灯枯这个词是不是用的很妙~]


到这里,其实小伙伴们一定会好奇,js是单线程执行的,生成器是如何让函数暂停,之后又恢复的捏,那我们就不得不提一下协程了。


Generator与协程


  1. 什么是协程?


协程是一种比线程更加轻量级的存在,协程处在线程的环境中,一个线程可以存在多个协程,可以将协程理解为线程中的一个个任务。不像进程和线程,协程并不受操作系统的管理,而是被具体的应用程序代码所控制。


  1. 生成器函数是如何让函数暂停,之后又恢复的呢?


一个线程一次只能执行一个协程。比如当前执行 A 协程,另外还有一个 B 协程,如果想要执行 B 的任务,就必须在 A 协程中将JS 线程的控制权转交给 B协程,那么现在 B 执行,A 就相当于处于暂停的状态。


总之,大概的流程就是这样:


  1. 协程A开始执行
  2. 协程A暂停,协程B开始执行
  3. 协程B执行完,交给协程C执行
  4. 协程C执行完,执行权交回给协程A继续执行


Generate自动执行(执行器)


ok,那我们现在去做一件事,我们利用generator把异步的任务封装成一个方法,之后自动的去按顺序(即类似同步方式)去进行调用。


function* asyncFun() {
    let resA = yield PromiseA //promise对象
    let resB = yield PromiseB //promise对象
    let resC = yield PromiseC //promise对象
}


如上面代码所示,我们把我们要依次执行的异步任务按照同步任务的写法写了出来,现在我们要去写一个方法,让他们自动按顺序执行~


function asyncToSyncAndRun(gen){
  var g = gen(); //此时g为生成器对象
  function next(data){
    var result = g.next(data);
    //注意:前面说过 result的结构,result是一个对象,里面的value对应yield后表达式的返回值
    //所以result.value是一个Promise对象
    if (result.done) return result.value;//如果遍历结束,return
    //未遍历结束,就把下一个next执行放在现在的Promise对象的回调中去
    result.value.then(function(data){
      next(data);
    });
  }
  next();//触发next方法~
}
//自动执行
asyncToSyncAndRun(asyncFun)


于是,通过上面的工具方法asyncToSyncAndRun,并借助generator,我们可以把异步任务已同步任务的写法串联起来~

相关文章
|
1月前
|
前端开发 JavaScript 开发者
Async 和 Await 是基于 Promise 实现
【10月更文挑战第30天】Async和Await是基于Promise实现的语法糖,它们通过简洁的语法形式,借助Promise的异步处理机制,为JavaScript开发者提供了一种更优雅、更易于理解和维护的异步编程方式。
27 1
|
29天前
|
前端开发
如何使用async/await解决Promise的缺点?
总的来说,`async/await` 是对 Promise 的一种很好的补充和扩展,它为我们提供了更高效、更易读、更易维护的异步编程方式。通过合理地运用 `async/await`,我们可以更好地解决 Promise 的一些缺点,提升异步代码的质量和开发效率。
35 5
|
29天前
|
前端开发 JavaScript
async/await和Promise在性能上有什么区别?
性能优化是一个综合性的工作,除了考虑异步模式的选择外,还需要关注代码的优化、资源的合理利用等方面。
38 4
|
1月前
|
JSON 前端开发 JavaScript
浅谈JavaScript中的Promise、Async和Await
【10月更文挑战第30天】Promise、Async和Await是JavaScript中强大的异步编程工具,它们各自具有独特的优势和适用场景,开发者可以根据具体的项目需求和代码风格选择合适的方式来处理异步操作,从而编写出更加高效、可读和易于维护的JavaScript代码。
32 1
|
2月前
|
前端开发 JavaScript
setTimeout、Promise、Async/Await 的区别
`setTimeout` 是用于延迟执行函数的简单方法;`Promise` 表示异步操作的最终完成或失败;`Async/Await` 是基于 Promise 的语法糖,使异步代码更易读和维护。三者都用于处理异步操作,但使用场景和语法有所不同。
|
2月前
|
前端开发 JavaScript 开发者
JavaScript 中的异步编程:深入了解 Promise 和 async/await
【10月更文挑战第8天】JavaScript 中的异步编程:深入了解 Promise 和 async/await
|
2月前
|
前端开发 JavaScript UED
深入了解JavaScript异步编程:回调、Promise与async/await
【10月更文挑战第11天】深入了解JavaScript异步编程:回调、Promise与async/await
24 0
|
3月前
|
前端开发 JavaScript
解决异步问题,教你如何写出优雅的promise和async/await,告别callback回调地狱!
该文章教授了如何使用Promise和async/await来解决异步编程问题,从而避免回调地狱,使代码更加清晰和易于管理。
解决异步问题,教你如何写出优雅的promise和async/await,告别callback回调地狱!
|
5月前
|
前端开发 JavaScript
Vue 中 Promise 的then方法异步使用及async/await 异步使用总结
Vue 中 Promise 的then方法异步使用及async/await 异步使用总结
164 1
|
4月前
|
前端开发 JavaScript 开发者
探索前端开发中的异步编程:Promise与Async/Await
在现代前端开发中,处理异步操作是至关重要的。本文将深入探讨异步编程的核心概念,重点比较JavaScript中的Promise与Async/Await两种异步编程方式。通过实例和比较,读者将能够理解这两种方法的优缺点,如何在实际开发中选择适合的异步编程模式,从而编写更简洁、可维护的代码。
下一篇
DataWorks