AI智能
改变未来

Part 1 · JavaScript深度剖析模块一:函数式编程与 JS 异步编程、手写 Promise

函数式编程与 JS 异步编程、手写 Promise

  • Part 1 · JavaScript 深度剖析
  • 模块一:函数式编程与 JS 异步编程、手写 Promise
  • 任务一:函数式编程范式
  • 1. 课程介绍
  • 2. 为什么要学函数式编程
  • 3. 函数式编程概念
  • 4. 函数是一等公民
  • 5. 高阶函数-函数作为参数
  • 6. 高阶函数-函数作为返回值
  • 7. 高阶函数的意义
  • 8. 常用的高阶函数
  • 9. 闭包-概念
  • 10. 闭包-案例
  • 11. 纯函数概念
  • 12. Lodash
  • 13. 纯函数的好处
  • 14. 副作用
  • 15. 柯里化
  • 16. Lodash中的柯里化方法
  • 17. 柯里化案例
  • 18. 柯里化原理模拟
  • 19. 柯里化总结
  • 20. 函数组合概念
  • 21. Lodash中的组合函数
  • 22. 组合函数原理模拟
  • 23. 函数组合-结合律
  • 24. 组合函数-调试
  • 25. Lodash-fp模块
  • 26. Lodash-map方法的小问题
  • 27. Pointfree
  • 28. Pointfree-案例
  • 29. Functor
  • 为什么要学函子
  • 什么是 Functor
  • 30. Functor总结
  • 31. MayBe函子
  • 32. Either函子
  • 33. IO函子
  • 34. Folktale
  • 35. Task函子
  • 36. Pointed函子
  • 37. IO函子问题
  • 38. Monad函子
  • 39. 总结
  • 40. 随堂测试
  • 任务二:JavaScript 异步编程
    • 1. 概述
    • 2. 同步模式
    • 3. 异步模式
    • 4. 回调函数
    • 5. Promise 概述
    • 6. Promise 基本用法
    • 7. Promise 使用案例
    • 8. Promise 常见误区
    • 9. Promise 链式调用
    • 10. Promise 异常处理
    • 11. Promise 静态方法
    • 12. Promise 并行执行
    • 13. Promise 执行时序
    • 14. Generator 异步方案(上)
    • 15. Generator 异步方案(中)
    • 16. Generator 异步方案(下)
    • 17. Async 函数
    • 18. 随堂测试
  • 任务三:手写 Promise 源码
    • 1. Promise 类核心逻辑实现
    • 2. 在 Promise 类中加入异步逻辑
    • 3. 实现 then 方法多次调用添加多个处理函数
    • 4. 实现 then 方法的链式调用(一)
    • 5. 实现 then 方法的链式调用(二)
    • 6. then 方法链式调用识别 Promise 对象自返回
    • 7. 捕获错误及 then 链式调用其他状态代码补充
    • 8. 将 then 方法的参数变成可选参数
    • 9. Promise.all 方法的实现
    • 10. Promise.resolve 方法的实现
    • 11. finally 方法的实现
    • 12. catch 方法的实现

    文章内容输出来源:拉勾前端高薪训练营

    Part 1 · JavaScript 深度剖析

    模块一:函数式编程与 JS 异步编程、手写 Promise

    任务一:函数式编程范式

    1. 课程介绍

    • 为什么要学习函数编程以及什么是函数式编程
    • 函数式编程的特性(纯函数、柯里化、函数组合等)
    • 函数式编程的应用场景
    • 函数式编程库 Lodash

    2. 为什么要学函数式编程

    • 随着React的流行受到的关注越来越多
    • Vue3也开始使用
    • 函数式编程可以抛弃this
    • 打包过程中可以更好的利用tree shaking过滤无用的代码
    • 方便测试、方便并行处理
    • 有很多库可以帮助我们进行函数式开发:lodash、underscore、ramda

    3. 函数式编程概念

    函数式编程是编程范式之一,还有我们熟悉的面向过程和面向对象编程。

    • 面向对象编程的思维方式:把现实世界中的事物抽象成程序世界中的类和对象,通过封装、继承和多态来演示事物事件的联系。
    • 函数式编程的思维方式:把现实世界中的事物和事物之间的联系抽象到程序世界(对运算过程进行抽象)
      – 程序的本质:根据输入通过某种运算符获得相应的输出,程序开发过程中会涉及很多有输入和输出的函数
      – x -> f(联系、映射) -> y,y=f(x)
      函数式编程中的函数指的不是程序中的函数(方法),而是数学中的函数即映射关系,例如:y = sin(x),x和y的关系
      相同的输入始终要得到相同的输出(纯函数)
      – 函数式编程是用来描述数据(函数)之间的映射
    // 非函数式let num1 = 1;let num2 = 2;let sum = num + num1;console.log(sum);// 函数式function add(a,b) {return a+ b}console.log(add(1,2));

    4. 函数是一等公民

    • 函数可以存储在变量中
    • 函数作为参数
    • 函数作为返回值

    在Javascript中函数就是一个普通对象(可以通过 new Function()),我们可以把函数存储到变量中/数组中,他还可以作为一个函数的参数和返回值,甚至我们可以在远程运行的时候通过new Function(‘alert(1)’)来构造一个新函数。

    • 把函数赋值给变量
    // 把函数赋值给变量let fn = function(){console.log(\'hello world\');}// 例子const BlogController = {index(posts){return views.index(posts)},show(post){return views.show(post)},create(attrs){return Db.create(attrs)},update(post,attrs){return Db.update(post,attrs)},destory(post){return Db.destory(post)},}// 简写优化// 函数里面包裹另一个函数形式类似并返回,那么就可以认为这两个函数是同一个const BlogController = {index: views.index,show: views.show,create: Db.create,update: Db.update,destory: Db.destory}

    5. 高阶函数-函数作为参数

    • 什么是高阶函数

      可以把函数作为参数传递给另一个函数

    • 可以把函数作为另一个函数的返回结果
  • 函数作为参数

  • // 函数作为参数function forEach(arr, fn){for (const item of arr) {fn(item)}}let arr1 = [1,2,3,4,6]forEach(arr1,(item) => {console.log(item);})// 函数作为参数function filters(arr, fn){let result = []for (const item of arr) {if (fn(item)) {result.push(item)}}return result}let arr2 = [2,3,5,6,8,9]const newArr = filters(arr2, (item) => {if (item % 2 === 0) {return false} else {return true}})console.log(newArr);
    • 函数作为返回结果
    //  函数作为返回结果function makeFn() {const str = \'hello world\'return function() {console.log(str);}}const fn = makeFn();fn();// 也可以直接调用// makeFn()();//  函数作为返回结果,而且只执行一次function once(fn) {let flag = false;return function (){if(!flag){flag = true;// 就是return的这个函数的参数也就是pay(9)中的参数9,虽然return的这个函数的没有使用形参接收但是通过arguments还是能获取到的// apply通俗一点讲就是:用return的这个函数去执行fn里面的内容.return fn.apply(this,arguments)}}}const pay = once((money) => {console.log(`支付:${money}RMB`);})// 只会支付一次pay(9);pay(2);pay(6);

    6. 高阶函数-函数作为返回值

    //  函数作为返回值function makeFn() {const str = \'hello world\'return function() {console.log(str);}}const fn = makeFn();fn();// 也可以直接调用// makeFn()();//  函数作为返回值,而且只执行一次function once(fn) {let flag = false;return function (){if(!flag){flag = true;// 就是return的这个函数的参数也就是pay(9)中的参数9,虽然return的这个函数的没有使用形参接收但是通过arguments还是能获取到的// apply通俗一点讲就是:用return的这个函数去执行fn里面的内容.return fn.apply(this,arguments)}}}const pay = once((money) => {console.log(`支付:${money}RMB`);})// 只会支付一次pay(9);pay(2);pay(6);

    7. 高阶函数的意义

    • 抽象可以帮我们屏蔽细节,只需要关注我们的目标
    • 可以用来抽象通用的问题
    // 面向过程的方式// 这种方式我们在遍历的时候还要去关注 i的大小等等这些细节let array = [1, 2, 3, 4]for (let i = 0; i < array.length; i++) {console.log(array[i])}// 高阶函数function forEach(arr, fn){for (const item of arr) {fn(item)}}// 而我们封装好的高阶函数在使用的时候就不用去关注i的大小等这些细节,因为内部已经封装好了let arr1 = [1,2,3,4,6]forEach(arr1,(item) => {console.log(item);})function filters(arr, fn){let result = []for (const item of arr) {if (fn(item)) {result.push(item)}}return result}let arr2 = [2,3,5,6,8,9]const newArr = filters(arr2, (item) => {if (item % 2 === 0) {return false} else {return true}})console.log(newArr);

    8. 常用的高阶函数

    • forEach
    • map
    • filter
    • every
    • some
    • find/findIndex

    • 自己手写几个高阶函数
    // 手写高阶函数// 把一些个性化的需求提取出来比如map是乘以2还是除以2这些交给调用者去个性化制定// map是对每一项进行处理function map (arr,fn) {let result = [];for (const item of arr) {result.push(fn(item))}return result}let arr1 = [1,5,6,7]const newArr = map(arr1,(item) => {return item * 2})console.log(newArr);// 只要有一个满足条件就返回truefunction some(arr, fn){let flag = false;for (const item of arr) {// 只要有一个满足就可以退出循环了if (fn(item)) {flag = true;break}}return flag}let arr2 = [4,5,6,3,9]const flag2 = some(arr2, (item) => {return item < 1})console.log(flag2);// 所有满足条件才返回true,只要有一个不满足就返回falsefunction every(arr, fn){let flag = true;for (const item of arr) {// 只要有一个不满足就可以退出循环了if (!fn(item)) {flag = false;break}}return flag}let arr3 = [4,5,2,8,9]const flag3 = every(arr3, (item) => {return item < 10})console.log(flag3);

    9. 闭包-概念

    • 闭包:函数和其周围的状态(词法环境)的引用捆绑在一起形成闭包。简单说例如一个函数makeFn返回另一个函数并且调用了makeFn函数里面的成员则就是闭包作用:可以在另一个作用域调用一个函数内部并访问到该函数的作用域中的成员。例如,也就是延长了外部函数makeFn中的内部变量str的作用范围
    • 闭包的本质:函数在执行的时候会放到一个执行栈上当函数执行完毕之后会从执行栈上移除,但是
      堆上的作用域成员因为被外部引用不能释放
      ,因此内部函数依然可以访问外部函数的成员
    // 这个例子中当make执行完后这个函数的内部成员就会被释放掉。function make() {const str = \'hello world\';}make();//  但是当一个函数返回了另一个函数并且访问了外部函数的成员的话,就不会被释放掉function makeFn() {const str = \'hello world\'return function() {console.log(str);}}// 这里调用了makeFn而fn其实就是返回来的那个函数,而返回来的内部函数访问了外部函数的str这个成员,所以就不会被释放掉const fn = makeFn();fn();// 也可以直接调用// makeFn()();//  函数作为返回值,而且只执行一次function once(fn) {let flag = false;return function (){if(!flag){flag = true;// 就是return的这个函数的参数也就是pay(9)中的参数9,虽然return的这个函数的没有使用形参接收但是通过arguments还是能获取到的// apply通俗一点讲就是:用return的这个函数去执行fn里面的内容.return fn.apply(this,arguments)}}}const pay = once((money) => {console.log(`支付:${money}RMB`);})// 只会支付一次pay(9);pay(2);pay(6);

    10. 闭包-案例

    案例:生成计算数字的多少次幂的函数

    // 闭包的案例function makePower(power){return function(num){return Math.pow(num, power)}}// 使用闭包可以封装一些较为常用的方法,例如我们在使用Math.pow求一个数的n次幂时,有一些求平方和立方是使用频率比较多的,但是我们每次使用时都要传递两个参数这样还是比较麻烦的所以我们可以通过闭包进行封装出一个方法专门用于求平方和立方,封装后只需要传递需要求幂的数字这一个参数就可以了const makePower2 = makePower(2);const makePower3 = makePower(3);// 调用函数内部的返回的函数形成闭包const result2 = makePower2(9);const result3 = makePower3(9);console.log(result2);console.log(result3);

    在浏览器打断点调试得到如下图:

    11. 纯函数概念

    • 纯函数:纯函数都需要有参数和返回值,相同的输入永远会得到相同的输出,而且没有任何可观察的副作用纯函数就是类似数学中的函数(用来描述输入和输出之间的关系),y = f(x)
    // 纯函数let number = [1,2,4,6,7,4];// 截取从下标0到3的值(不包括下标3)并返回截取部分的数组,不会改变原数组number.slice(0,3);// => [1,2,4]number.slice(0,3);// => [1,2,4]number.slice(0,3);// => [1,2,4]// 非纯函数let number2 = [1,2,4,6,7,4];// 截取下标0开始截取3个,改变原数组,返回被截取部分的数组number2.splice(0, 3);// => [1,2,4]number2.splice(0, 3);// => [6,7,4]number2.splice(0, 3);// => []// 手写一个纯函数function add(a,b){return a + b;}add(1,2);// => 3add(1,2);// => 3add(1,2);// => 3
    • 函数式编程不会保留计算中间的结果,所以变量是不可变的(无状态的)
    • 我们可以把一个函数的执行结果交给另一个函数去处理

    12. Lodash

    Lodash是一个一致性、模块化、高性能的JavaScript实用工具库(lodash 的 fp 模块提供了对函数
    式编程友好的方法),提供了对数组、数字、对象、字符串、函数等操作的一些方法,这些都是纯函数。

    const _ = require(\'lodash\');let arr = [4,1,6,3,4,5]// 查找第一个并返回const result1 = _.first(arr)// console.log(result1);// 循环,参数一是要循环的参数,参数二是个函数:itme每一项,index索引;返回循环的函数const result2 = _.each(arr,(itme, index) => {// console.log(itme);console.log(index);})console.log(result2);// 转成大写const result3 = _.toUpper(_.first(arr))

    13. 纯函数的好处

    • 可缓存因为纯函数对相同的输入始终有相同的结果,所以可以把纯函数的结果缓存起来
    // 记忆函数:计算了一次就会把结果缓存下来,不会在进行计算const _ = require(\'lodash\');// 计算园面积的函数function getArea(r){console.log(r);return Math.PI * r * r}// 接收一个函数为参数,返回一个纯函数// const getAreaFn = _.memoize(getArea);// console.log(getAreaFn(4));// console.log(getAreaFn(4));// console.log(getAreaFn(4));// console.log(getAreaFn(4));
    • 模拟一个memoize函数
    // 自己手写memoize// 首先他接收一个函数作为参数,还会返回一个函数,再则要判断是否已经计算了,计算了的话,返回一个结果,否则就执行传递过来的函数,再把执行结果返回function memoize(fn) {let obj = {}const key = JSON.stringify(arguments)return function(){obj[key] = obj[key] || fn.apply(fn, Array.from(arguments))return obj[key]}}const getAreaFn = memoize(getArea);console.log(getAreaFn(4));console.log(getAreaFn(4));console.log(getAreaFn(4));console.log(getAreaFn(4));

    打印结果如下:

    • 可测试纯函数让测试更方便
  • 并行处理
      在多线程的环境下并行操作共享的内存数据很可能会出现意外情况
    • 纯函数不需要访问共享的内存数据,所以在并行的环境下可以任意的运行纯函数(Web Worker)

    14. 副作用

    • 纯函数对于相同的输入永远会得到相同的输出,而且没有任何可观察的副作用
    // 不纯的// 因为函数依赖外部变量mini,所以即使我们传入相同的参数但是只要改变了外部变量mini也就得不到相同的结果了let mini = 18function checkAge (age) {return age >= mini}// 纯函数// 把mini提取到函数内部去,就变成纯函数了(有硬编码,后续可以通过柯里化解决)// 就是我们平常讲的写死function checkAge (age) {let mini = 18return age >= mini}

    副作用会让一个函数变得不纯,纯函数根据相同的输入得到相同的输出,如果函数内部依赖于外部的状态就无法保证输出相同,就会带来副作用。
    副作用的来源

    • 配置文件
    • 数据库
    • 获取用户输入

    • 所有外部交互都可能带来副作用,副作用会降低方法的通用性,同时会带来安全隐患和不确定性。但是副作用不可能完全禁止。

    15. 柯里化

    • 当一个函数有多个参数的时候先传递一部分参数调用他(这部分参数以后永远不会变例如下面例子的18和20)
    • 然后返回的一个新的函数接收剩余的参数,返回结果
    // 有硬编码的纯函数function checkAge (age) {let mini = 18return age >= mini}// 使用函数柯里化决解硬编码function checkAge(mini){return function(age){return age >= mini}}const checkAge18 = checkAge(18);const checkAge20 = checkAge(20);console.log(checkAge18(15));console.log(checkAge18(30));console.log(checkAge20(15));console.log(checkAge20(30));

    16. Lodash中的柯里化方法

    • _.curry(func)功能:创建一个函数,该函数接受一个或多个参数,如果func的所有参数都被提供则执行func并返回一个结果。否则继续返回该函数并等待接收剩余的参数
    • 参数:需要柯里化的函数, 如果传入的函数是纯函数的话返回的也是一个纯函数
    • 返回值:柯里化后的函数

    17. 柯里化案例

    // 案例// lodash中的柯里化函数const _ = require(\'lodash\');function getSum(a, b, c){return a + b + c}const curried = _.curry(getSum);console.log(curried(1,2,3)); // 6console.log(curried(1)(2,3)); // 6console.log(curried(1,2)(3)); // 6

    18. 柯里化原理模拟

    // lodash的curry函数// const _ = require(\'lodash\');// function getSum(a, b, c){//     return a + b + c// }// const curried = _.curry(getSum);// console.log(curried(1,2,3)); // 6// console.log(curried(1)(2,3)); // 6// console.log(curried(1,2)(3)); // 6// 自己实现curry函数/*首先curry要接收一个参数fn函数,而这个参数fn函数的参数的个数就是我们需要的形参个数,然后返回一个柯里化后的函数curried,而这个函数的参数就是我们实际传递的实参个数,当实参个数小于形参个数时会再次返回一个柯里化后的函数curriedFn传递过去的参数则是上一次加上当前传递的参数的合并的值,此时并不会调用fn,直到实参大于等于形参后才真正调用fn这个函数返回结果*/function getSum(a, b, c){return a + b + c}function curry(fn){return function curriedFn(...args){// 如果实参小于形参if (args.length < fn.length) {return function(){// 第一次调用的参数会保存在args,当再次调用的时候是返回的这个匿名函数,// 这是我们通过arguments就能再次获取到参数,然后把上一次调用的参数args和返回后再次调用的这个匿名函数的参数arguments进行合并后// 再次调用curriedFnreturn curriedFn(...args.concat(Array.from(arguments)))}}// 实参不小于形参后就执行fn并返回结果return fn(...args)}}const curried = curry(getSum);console.log(curried(1,2,3)); // 6console.log(curried(1,2)(3)); // 6console.log(curried(1)(3)(2)); // 6

    19. 柯里化总结

    • 柯里化可以让我们给一个函数传递较少的参数得到一个通过闭包记住了某些固定参数的新函数
    • 这是一种对函数参数的‘缓存’
    • 让函数变的更灵活,让函数的粒度更小
    • 可以把多元函数转换成一元函数,可以组合使用函数产生强大的功能

    20. 函数组合概念

    • 纯函数和柯里化很容易写出洋葱代码即h(g(f(x)))

      例如获取数组的最后一个元素再转换成大写字母,.toUpper(.first(_.reverse(array)))

  • 函数组合刻意让我们把细粒度的函数重新组合生成一个新的函数

  • 函数组合:如果一个函数要经过多个函数处理才能得到最终值,这个时候可以把中间过程的函数合并到一个函数

      函数就像是数据的管道,函数组合就是把这些管道连接起来,让数据穿过多个管道形成最终的结果
    • 函数组合默认就是从右到左
    // 函数组合function compose(f,g){return function(val){return f(g(val))}}function reverse(arr){return arr.reverse()}function first(arr){return arr[0]}let arr = [1,2,3,4,5]const last = compose(first,reverse);console.log(last(arr));

    21. Lodash中的组合函数

    • lodash 中组合函数 flow() 或者 flowRight(),他们都可以组合多个函数。flow() 是从左到右运行;flowRight() 是从右到左运行,使用的更多一些
    // lodash中的函数组合const _ = require(\'lodash\');const reverse = arr => arr.reverse()const first = arr => arr[0];const toUpperCase = str => str.toUpperCase()// 调用flowRight会返回一个函数UpperCaseconst UpperCase = _.flowRight(toUpperCase,first,reverse);// 调用UpperCase并传递要操作的数据const result = UpperCase([\'nihao\',\'hello\',\'world\'])console.log(result);

    22. 组合函数原理模拟

    // // lodash中的函数组合// const _ = require(\'lodash\');// const reverse = arr => arr.reverse()// const first = arr => arr[0];// const toUpperCase = str => str.toUpperCase()// // 调用flowRight会返回一个函数UpperCase// const UpperCase = _.flowRight(toUpperCase,first,reverse);// // 调用UpperCase并传递要操作的数据// const result = UpperCase([\'nihao\',\'hello\',\'world\'])// console.log(result);// 自己写实现lodash中的flowRight方法// function compose(...args){//     return function(val){//        // acc累计器是上次执行回调的结果,没有默认值的话第一次默认为数组的第一项,这里默认的是val也就是用户传进来的数据,fn数组的当前项//         return args.reverse().reduce(function(acc,fn){//             return fn(acc)//         },val)//     }// }// 箭头函数写法const compose = (...args) => val => args.reverse().reduce((acc,fn) => fn(acc),val)const reverse = arr => arr.reverse()const first = arr => arr[0];const toUpperCase = str => str.toUpperCase()// 调用flowRight会返回一个函数UpperCaseconst UpperCase = compose(toUpperCase,first,reverse);// 调用UpperCase并传递要操作的数据const result = UpperCase([\'nihao\',\'hello\',\'world\'])console.log(result);

    23. 函数组合-结合律

    // 函数组合的结合律const _ = require(\'lodash\')// 前提条件是总的顺序不能乱,虽然进行了不同的结合但是总的顺序还是toUpper,first,reverse所以都是没问题的// const UpperCase = _.flowRight(_.toUpper,_.first,_.reverse);// const UpperCase = _.flowRight(_.toUpper,_.flowRight(_.first,_.reverse));const UpperCase = _.flowRight(_.toUpper,_.first,_.flowRight(_.reverse));// 而下面是reverse,toUpper,first总的顺序乱了所以会报错// const UpperCase = _.flowRight(_.reverse,_.flowRight(_.toUpper,_.first));// 调用UpperCase并传递要操作的数据const result = UpperCase([\'nihao\',\'hello\',\'world\'])console.log(result);

    24. 组合函数-调试

    // 转换 NEVER SAY DIE   =>  never-say-dieconst _ = require(\'lodash\');/*** 思路: 转换成上面需要先把split按指定符号把字符串切割成数组[ \'NEVER\', \'SAY\', \'DIE\' ],* 然后通过_.toLower转成“never,say,die”,在通过map转成数组[ \'never\', \'say\', \'die\' ]* 最后通过join把数组按‘-’转成字符串never-say-die*/// 当使用函数组合时报错了怎么打印报错信息。可通过以下方法// 当打印地方多时,这种打印结果不容易分辨// function log(v){//     console.log(v);//     return v// }// 解决了打印地方多,不容易分辨的问题即传递表示过去const trace = _.curry((str,v) => {console.log(str,v);return v})// 为什么要使用柯里化比且还把行参的顺序进行了调换例如sep,str 换成str,sep// 因为组合函数只能接收一个参数的纯函数,使用柯里化就可以以把多元函数转换成一元函数也就是一个参数的函数,至于把形参调换是因为在函数组合完成后才会调用这个函数的时候才会传入字符串const split = _.curry((sep,str) => _.split(str,sep));const join = _.curry((str,arr) => _.join(arr,str));const map = _.curry((arr,fn) => _.map(fn,arr));// flowRight的执行顺序从右往左,会先执行split(\' \'),因为这是个柯里化后的函数,他接收两个参数,但是目前只传了一个所以还是会返回一个函数,其他也是这样所以最终flowRight的参数都是一个一元函数const f = _.flowRight(join(\'-\') ,trace(\"map之后\"),map(_.toLower),trace(\"split之后\"),split(\' \'));// 最后在调用这个组合函数并传递字符串,此时像split这些柯里化后的函数就会执行并返回结果了,最后打印出来console.log(f(\'NEVER SAY DIE\'));

    25. Lodash-fp模块

    在我们上一节使用函数组合时,是自己对lodash的一些方法进行了封装,lodash中的方法没有进行柯里化以及这些方法的参数是数据优先,函数最后。如果我们每次使用lodash进行函数组合都要自己在进行一次封装的话就很麻烦,Lodash-fp模块就是解决这一问题的

    • lodash/fp模块lodash的fp模块提供了实用的对函数式编程友好的方法
    • 提供了不可变auto-curried iteratee-first data-last即自动柯里化,参数中函数优先,数据最后的方法
    // lodash 模块const _ = require(\'lodash\')_.map([\'a\', \'b\', \'c\'], _.toUpper)// => [\'A\', \'B\', \'C\']_.map([\'a\', \'b\', \'c\'])// => [\'a\', \'b\', \'c\']_.split(\'Hello World\', \' \')// lodash/fp 模块// const fp = require(\'lodash/fp\')// fp.map(fp.toUpper, [\'a\', \'b\', \'c\'])// fp.map(fp.toUpper)([\'a\', \'b\', \'c\'])// fp.split(\' \', \'Hello World\')// fp.split(\' \')(\'Hello World\')// 使用lodash/fp 模块 转换 NEVER SAY DIE   =>  never-say-dieconst fp = require(\'lodash/fp\');// flowRight的执行顺序从右往左,会先执行split(\' \'),因为这是个柯里化后的函数,他接收两个参数,但是目前只传了一个所以还是会返回一个函数,其他也是这样所以最终flowRight的参数都是一个一元函数const f = fp.flowRight(fp.join(\'-\'),fp.map(fp.toLower),fp.split(\' \'));// 最后在调用这个组合函数并传递字符串,此时像split这些柯里化后的函数就会执行并返回结果了,最后打印出来console.log(f(\'NEVER SAY DIE\'));

    26. Lodash-map方法的小问题

    // lodash 模块的map方法const _ = require(\'lodash\');/***  这是因为lodash的map接收两个参数分别是数组和回调函数parseInt需要传递3个参数分别是当前处理项、索引、当前数组,* 而第二个参数表示要解析的数字的基数是或其值为 0,则数字将以 10 为基础来解析。* 如果它以 “0x” 或 “0X” 开头,将以 16 为基数。如果该参数小于 2 或者大于 36,则 parseInt() 将返回 NaN*/console.log(_.map([\'32\',\'54\',\'4\'],parseInt)); // [ 32, NaN, NaN ]// lodash/fp 模块的map方法const fp = require(\'lodash/fp\');// 这是因为lodash/fp的回调函数parseInt直接收一个参数即当前处理项console.log(fp.map(parseInt, [\'32\',\'54\',\'4\'])); // [ 32, 54, 4 ]

    27. Pointfree

    Point Free:把数据处理的过程定义成与数据无关的合成运算,不需要用到代表数据的那个参数,只要把简单的运算步骤合成到一起,在使用这种模式之前我们需要定义一些辅助的基本运算函数。

    • 不需要指明处理的数据
    • 只需要合成运算过程
    • 需要定义一些辅助的基本运算函数
      参考:Pointfree
    // Hello    World  =>  hello_world// 非 Point Free 模式// Hello World => hello_worldfunction f (word) {return word.toLowerCase().replace(/\\s+/g, \'_\');}// Point Free 模式const fp = require(\'lodash/fp\');// 在组合函数的时候不需要指明处理的数据const f = fp.flowRight(fp.replace(/\\s+/g,\'_\'),fp.toLower)// 调用的时候再传递数据console.log(f(\'Hello    World\'));

    28. Pointfree-案例

    // Pointfree 案例1// \'world wild web\'  =>  W. W. W// const fp = require(\'lodash/fp\');// const result = fp.flowRight(fp.join(\'. \'),fp.map(fp.first),fp.map(fp.toUpper),fp.split(\' \'));// console.log(result(\'world wild web\')); // W. W. W// 上述代码相当于是下面的代码(便于理解)// const fp = require(\'lodash/fp\');// const str = \'world wild web\';// // 返回字符串数组// const aa = str.split(\' \');// const bb = aa.map((item,index,arr) =>{//     return fp.toUpper(item)// })// const cc = bb.map((item,index,arr) =>{//     return fp.first(item)// })// const dd = fp.join(\'. \')// console.log(aa); // [ \'world\', \'wild\', \'web\' ]// console.log(bb); // [ \'WORLD\', \'WILD\', \'WEB\' ]// console.log(cc); // [ \'W\', \'W\', \'W\' ]// console.log(dd(cc)); // W. W. W// 优化案例1// 这里使用了两次map去遍历数组,可以再次使用flowRight函数组合成一次const fp = require(\'lodash/fp\');const result = fp.flowRight(fp.join(\'. \'),fp.map(fp.flowRight(fp.first,fp.toUpper)),fp.split(\' \'));console.log(result(\'world wild web\')); // W. W. W

    29. Functor

    为什么要学函子

    我们之前学的都是函数式编程的一些基础,但是我们还没有演示在函数式编程中如何把副作用控制在可控的范围内、异常处理、异步操作等。

    什么是 Functor
    • 容器:包含值和值的变形关系(这个变形关系就是函数)
    • 函子:是一个特殊的容器,通过一个普通的对象来实现,该对象具有 map 方法,map 方法可以运
      行一个函数对值进行处理(变形关系)
    // 函子// 函子是一个特殊的容器通过普通对象即这里的Container来实现,这个对象有一个map方法// 构造函数创建一个私有属性_value进行接收值即这里的5。map方法就是那个变形关系,map内部会返回一个有Container创建的对象传递的参数是通过调用map传递的一个函数用于处理并返回数据即fn(this._value)// class Container {//     constructor(value){//         this._value = value;//     }//     map(fn){//         return new Container(fn(this._value))//     }// }// const result = new Container(5)// .map(x => x + 1)// .map(x => x * x)// console.log(result); // Container { _value: 36 }// 我们使用的是函数式编程但是上述看着像是面向对象编程,所以可以改进下class Container {// 静态方法可以直接调用static of (value){return new Container(value)}constructor(value){this._value = value;}map(fn){return Container.of(fn(this._value))}}// 这样调用时就没有使用new关键字了,虽然Container类里面还是有,但是那是封装的过程const result = Container.of(5).map(x => x + 1).map(x => x * x)console.log(result); // 36

    30. Functor总结

    • 函数式编程的运算不直接操作,而是由函子完成
    • 函子就是一个实现了map契约的对像
    • 我们可以把函子想象成一个盒子,这个盒子里封装了一个值
    • 想要处理盒子中的值。我们需要给盒子的map方法传递一个处理值的函数(纯函数)由这个函数来对值进行处理
    • 最终map方法返回一个包含新值的盒子(函数)

    上一节中的代码如果我们传入null或undefined

    class Container {static of (value){return new Container(value)}constructor(value){this._value = value;}map(fn){return Container.of(fn(this._value))}}// 不小心传入一个空值,此时就会报错,也就破环了纯函数,因为纯函数是相同输入得到相同输出,但是此时却报错了并没有输出,这就是副作用const result = Container.of(null).map(x => x.toUpperCase())console.log(result);

    31. MayBe函子

    • 我们在编程的过程中可能会遇到很多错误,需要对这些错误做相应的处理
    • MayBe函子的作用就是可以对外部的空值情况做处理(控制副作用在允许范围内)
    // MayBe 函子class MayBe {static of (value){return new MayBe(value)}constructor(value){this._value = value;}map(fn){return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value))}// 辅助函数用于判断传递的参数是否等于null或undefinedisNothing(){return this._value === null || this._value === undefined;}}// 不小心传入一个空值,此时就会报错,也就破环了纯函数,因为纯函数是相同输入得到相同输出,但是此时却报错了并没有输出,这就是副作用const result = MayBe.of(null).map(x => x.toUpperCase())console.log(result); // MayBe { _value: null }
    • 但是在MayBe函子中,我们很难确认是哪一步产生的空值问题,如下例:
    // MayBe 函子class MayBe {static of (value){return new MayBe(value)}constructor(value){this._value = value;}map(fn){return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value))}// 辅助函数用于判断传递的参数是否等于null或undefinedisNothing(){return this._value === null || this._value === undefined;}}// 在调用第二个map中返回一个null,打印并没有具体指出是哪一步的问题const result = MayBe.of(\'hello world\').map(x => x.toUpperCase()).map(x => null).map(x => x.split(\' \'))console.log(result);// => MayBe { _value: null }

    32. Either函子

    • Either两者中选择任意一个,类似于if…else…的处理
    • 异常会让函数变的不纯,Either函子可以用来做异常功能

    其实就是分别写两个函子然后跟据try…catch捕捉的结果而选择性的调用不同函子

    // Either 函子// 处理错误的函子class Left {static of (value){return new Left(value)}constructor(value){this._value = value;}map(){return this}}// 处理正确的函子class Right {static of (value){return new Right(value)}constructor(value){this._value = value;}map(fn){return Right.of(fn(this._value))}}function parseJSON(val){try {return Right.of(JSON.parse(val))} catch (e) {// 错误直接返回错误信息return Left.of({error: e.message})}}// 成功时const f = parseJSON(\'{\"name\":\"zf\"}\').map(x => x.name.toUpperCase())console.log(f); // Right { _value: \'ZF\' }// 错误时const f1 = parseJSON(\'{name:zf}\').map(x => x.name.toUpperCase())console.log(f1); // Left { _value: { error: \'Unexpected token n in JSON at position 1\' } }

    33. IO函子

    • IO函子中的_value是一个函数,这里是吧函数作为值来处理
    • IO函子可以把不纯的动作存储到_value中,延迟执行这个不纯的操作(惰性执行),包装当前的纯操作
    • 把不纯的操作交给调用者处理
    const fp = require(\'lodash\')// IO 函子// 处理正确的函子class IO {static of (x){console.log(1);return new IO(function(){console.log(4);return x})}// fn是of方法中调用IO构造函数是传入的函数constructor(fn){console.log(2);this._value = fn;}map(fn){console.log(3);console.log(fp.flowRight(fn,this._value)); // [Function]// 这里不是调用函数处理值而是把当前value和传进来的fn即p => p.execPath组合成一个新的函数,所以布调用of方法而是IO构造函数return new IO(fp.flowRight(fn,this._value))}}// node对象process进程/*** 当我们调用of的时候,他会把当前我们取值的的过程包装到一个函数里面,* 当我们需要的时候来获取这个process。然后调用map方法来获取process中的某个属性,* map参数p其实就是of中传入的这个值*/const f = IO.of(process).map(p => p.execPath);console.log(f._value()); // C:\\Program Files\\nodejs\\node.exe

    34. Folktale

    • Folktale一个标准的函数式编程库和lodash、ramda不同的是,他没有提供很多功能函数
    • 只提供了一些函数式处理的操作,例如:compose、curry等,一些函子Task、Either、MayBe等
    const fp = require(\'lodash\')// Folktale的compose、curry的使用const {compose, curry} = require(\'folktale/core/lambda\')function fn(a,b){return a + b}// 和lodash中的curry不同的是folktale的curry第一个参数是传入函数的参数个数const f = curry(2,fn);console.log(f(1,2)); // 3const c = compose(fp.toUpper,fp.first);console.log(c([\'one\', \'two\'])); // ONE

    35. Task函子

    • Task 异步执行这里以 2.3.2 来演示
    // Task 处理异步任务const fs = require(\'fs\');const {split, find} = require(\'lodash/fp\')const {task} = require(\'folktale/concurrency/task\');function readFile(filename){// 固定参数resolver对象有两个方法 reject和resolvereturn task(resolver => {// node里面的回调函数的参数是函数优先然后是数据fs.readFile(filename,\'utf-8\', (err,data) =>{if (err) resolver.reject(err);resolver.resolve(data)})})}readFile(\'package.json\').map(split(\'\\n\')).map(find(v => v.includes(\'version\'))).run().listen({// listen是监听函数:onRejected监听失败的函数,onResolved监听成功的函数onRejected: (err) => {console.log(err);},onResolved: (data) => {console.log(data);}})

    36. Pointed函子

    • pointed 函子是指实现了of静态方法的函子
    • of方法是为了避免使用new来创建对象,更深层的含义是of方法用来把值放到上下文Content(把值放到容器中,使用map来处理值)

    // Pointed 函子class Container {static of (value) {return new Container(value)}// ……}// 这个返回的结果就是上下文,将来在这个上下文中处理数据Contanier.of(2).map(x => x + 5)

    37. IO函子问题

    const fp = require(\'lodash\');const fs = require(\'fs\');class IO {static of(){return new IO(function(x) {return x})}constructor(fn){this._value = fn}map(fn){return new IO(fp.flowRight(fn,this._value))}}function readFile(filename){/*** 直接读取文件会有副作用让函数变得不纯,* 所以不直接读取文件而是返回一个IO函子,* 也就是让读取文件延迟去执行*/return new IO(function() {return fs.readFileSync(filename,\'utf-8\')})}function print(x){return new IO(function(){console.log(x);return x})}// cat的格式其实是 IO(IO(x));里面的IO就是print这个函数返回的函子外层的IO是readFile返回的函子const cat = fp.flowRight(print,readFile);// 这样调用不太好,所以需要继续优化console.log(cat(\'package.json\')._value()._value());

    38. Monad函子

    • Monad函子是可以变扁的Pointed 函子,IO(IO(x)),解决函子嵌套
    • 一个函子如果具有 join 和 of 两个方法并遵守一些定律就是一个 Monad
    const fp = require(\'lodash\');const fs = require(\'fs\');class IO {static of(){return new IO(function(x) {return x})}constructor(fn){this._value = fn}/*** 调用时把fn、this._value进行合并,然后返回一个IO函子**/map(fn){return new IO(fp.flowRight(fn,this._value))}/*** 因为IO函子在创建的时候需要接收一个函数,* 而当这个函数返回一个函子的时候我们就需要把它变成Monad函子。* 调用并返回一个函子* 写join的目的就是为了解决上一小节的问题*/join(){return this._value()}/*** 作用是同时调用map和join.* 需要调用map所以需要传递一个参数,调用map后会返回一个函子* 所以在调用join*/flatMap(fn){return this.map(fn).join()}}function readFile(filename){/*** 直接读取文件会有副作用让函数变得不纯,* 所以不直接读取文件而是返回一个IO函子,* 也就是让读取文件延迟去执行*/return new IO(function() {return fs.readFileSync(filename,\'utf-8\')})}function print(x){return new IO(function(){console.log(x);return x})}// 当我们要合并的这个函数它返回的是一个值就调用map,当返回的是函子就调用flatMapconst f = readFile(\'package.json\')// .map(fp.toUpper).map(x => x.toUpperCase()).flatMap(print).join();console.log(f);

    39. 总结

    40. 随堂测试

    任务二:JavaScript 异步编程

    1. 概述

    • 同步模式与异步模式同步和异步的差别就在于这条流水线上各个流程的执行顺序不同。
  • 事件循环与消息队列
  • 异步编程的几种方式
  • Promise异步方案、宏任务/微任务队列
  • Generator异步方案、Async/Await语法糖
  • 2. 同步模式

    在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;

    3. 异步模式

    异步任务指的是,不进入主线程、而进入\”任务队列\”(task queue)的任务,只有等主线程任务执行完毕,\”任务队列\”开始通知主线程,请求执行任务,该任务才会进入主线程执行。
    不会等待这个任务的结束才开始下一个任务,开启过后就立即执行下一个任务,后续逻辑一般会通过回调函数的方式定义,没有异步模式JavaScript语言就无法同时处理大量耗时任务

    1. 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
    2. 主线程之外,还存在一个\”任务队列\”(task queue)。只要异步任务有了运行结果,就在\”任务队列\”之中放置一个事件。
    3. 一旦\”执行栈\”中的所有同步任务执行完毕,系统就会读取\”任务队列\”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
    4. 主线程不断重复上面的第三步。

    4. 回调函数

    一个函数被作为参数传递给另一个函数(在这里我们把另一个函数叫做“otherFunction”),回调函数在otherFunction中被调用。

    /注意到click方法中是一个函数而不是一个变量//它就是回调函数$(\"#btn_1\").click(function() {alert(\"Btn 1 Clicked\");});//或者function click() { // 它就是回调函数alert(\"Btn 1 Clicked\");}$(\"#btn_1\").click(click);

    5. Promise 概述

    Promise 对象用于表示一个异步操作的最终完成或失败,及其结果值。
    Promise 构造函数接收一个函数executor为参数,这个函数有又接收两个函数作为参数 resolve 和 reject。Promise构造函数执行时立即调用executor 函数, resolve 和 reject 两个函数作为参数传递给executor(executor 函数在Promise构造函数返回所建promise实例对象前被调用)。resolve 和 reject 函数被调用时,分别将promise的状态改为fulfilled(完成)或rejected(失败)。executor 内部通常会执行一些异步操作,一旦异步操作执行完毕(可能成功/失败),要么调用resolve函数来将promise状态改成fulfilled,要么调用reject 函数将promise的状态改为rejected。如果在executor函数中抛出一个错误,那么该promise 状态为rejected。executor函数的返回值被忽略。
    一个Promise 有以下几种状态:

    - pending: 初始状态,既不是成功,也不是失败状态。- fulfilled: 意味着操作成功完成。- rejected: 意味着操作失败。

    6. Promise 基本用法

    // 打印順序是111 222 333,說明 111函數內部是個同步的,它的異步核心是resolve和reject函數const promise = new Promise((resolve,reject)=>{console.log(111);// 成功的承諾// resolve(333)// 失敗的承諾reject(new Error(\'失敗\'))})promise.then((data)=> {console.log(data);console.log(333);},(err) => {console.log(err);})console.log(222);

    7. Promise 使用案例

    // ajax案例function ajax(url){return new Promise(function(resolve,reject) {const xhr = new XMLHttpRequest();xhr.open(\'GET\',url);xhr.responseType = \'json\';xhr.onload = function(){if(this.status === 200){resolve(this.response)} else {reject(new Error(this.statusText))}}xhr.send();})}ajax(\'./datas.json\').then(function(data) {// 请求成功console.log(data);},function(err) {// 请求失败console.log(err);})

    8. Promise 常见误区

    本质是通过回调函数的定义的异步任务结束后所需执行的任务,通过then方法传递进去的。
    嵌套使用的方式是使用Promise最常见的错误

    9. Promise 链式调用

    • Promise对象的then方法会返回一个全新的Promise对象
    • 后面的then方法就是在为上一个then返回的Promise注册回调
    • 前面then方法中回调函数的返回值会作为后面then方法回调的参数
    • 如果回调中返回的是Promise,那么后面then方法的回调会等待它的结束
    // ajax案例function ajax(url){return new Promise(function(resolve,reject) {const xhr = new XMLHttpRequest();xhr.open(\'GET\',url);xhr.responseType = \'json\';xhr.onload = function(){if(this.status === 200){resolve(this.response)} else {reject(new Error(this.statusText))}}xhr.send();})}ajax(\'./datas.json\').then(function(data) {// 请求成功return data}).then(function(data) {// 前面then方法中回调函数的返回值会作为后面then方法回调的参数return \'foo\'// 可以省略不写},function(err) {}).then(function(data) {console.log(data); //foo})

    10. Promise 异常处理

    function ajax(url){return new Promise(function(resolve,reject) {const xhr = new XMLHttpRequest();xhr.open(\'GET\',url);xhr.responseType = \'json\';xhr.onload = function(){if(this.status === 200){resolve(this.response)} else {reject(new Error(this.statusText))}}xhr.send();})}// 不推荐// ajax(\'./datas.json\').then(function(data) {//     // 这里返回一个失败的//     return ajax(\'./datas11.json\')// },function(err) {//     console.log(err);//     // 这里并没有捕获到异常而是下一个then里面的失败回调函数才能捕获到// }).then(function(data) {//     console.log(222);// },function(err) {//     // 这里才能捕获到那个失败的异常//     console.log(err);// })// 推荐使用ajax(\'./dat11as.json\').then(function(data) {// 请求成功return data}).then(function(data) {console.log(222);// 因为Promise是每次都是返回一个新的Promise对象,// 所以catch也只是对前面then方法返回的Promise指定失败的回调// 只不过它们都是同一个Promise链上,所以只要前面有一个异常那么就会一直被传递,// 所以在这里就能捕获到异常}).catch(function(err) {// 这里能捕获到异常console.log(err);})

    11. Promise 静态方法

    • Promise.resolve
    • Promise.reject
    function ajax(url){return new Promise(function(resolve,reject) {const xhr = new XMLHttpRequest();xhr.open(\'GET\',url);xhr.responseType = \'json\';xhr.onload = function(){if(this.status === 200){resolve(this.response)} else {reject(new Error(this.statusText))}}xhr.send();})}// Promise.resolve() 就是一定会返回一个成功的回调Promise.resolve(\'foo\')// 上面代码等同于new Promise(function(resolve, reject) {resolve(\'foo\')})// 如果Promise.resolve()传入的是个Promise对象,那么他会直接返回传入的那个对象const promise = ajax(\'./datas.json\')const promise2 = Promise.resolve(promise)console.log(promise === promise2); // true// Promise.reject() 就是一定会返回一个失败的回调Promise.reject(\'anythig\').catch(function(error) {console.log(error); // anythig})

    12. Promise 并行执行

    • Promise.all 将多个请求合并成一个请求,接收一个数组,数组的每一项是个Promise对象。只有等待所有任务结束后才会结束,所有请求都成功了all才会执行成功的回调,只要有一个请求失败all就执行失败的回调
    • Promise.race 将多个请求合并成一个请求,接收一个数组,数组的每一项是个Promise对象,只要有一个结束任务就会race就会执行对应的回调
    function ajax(url){return new Promise(function(resolve,reject) {const xhr = new XMLHttpRequest();xhr.open(\'GET\',url);xhr.responseType = \'json\';xhr.onload = function(){if(this.status === 200){resolve(this.response)} else {reject(new Error(this.statusText))}}xhr.send();})}// Promise.allconst promise = ajax(\'./urls.json\').then(function(data) {// console.log(data);// 返回一个数组,数组的每一项是传入的这个对象的每一个键值对的值const urls = Object.values(data);// 把字符串数组转成每一项都是Promise对象的数组const tasks = urls.map(item => ajax(item))// console.log(tasks);return Promise.all(tasks)}).then(function(data) {// 拿到当前Promise.all中每一个异步请求的结果的数据console.log(data);})// Promise.race// 这是能成功执行的promise函数const promise = ajax(\'./urls.json\');// 这个1秒后执行的失败的promise函数const reject = new Promise(function(resolve,reject) {setTimeout(() => {reject(new Error(\'timeout\'))}, 1000);})// 只要有一个结束,就会race就会调用对应的函数。可以用作ajax请求超时// 例如这里如果promise这个函数没在1秒前执行完的话,那么就会执行reject,// 然后race的catch就会捕捉到失败的异常.Promise.race([promise,reject]).then(val => {console.log(val);}).catch(err => {console.log(err);})

    13. Promise 执行时序

    优先级:同步代码 > 微任务(Promise、MutationObserver以及nodejs中的process.nextTick) > 宏任务(setTimeout以及大部分异步调用的api)
    微任务的作用是提高整体的相应能力。

    14. Generator 异步方案(上)

    function* test() {console.log(444);const cs1s = yield \'foo\';console.log(cs1s); // pppppp// try {//     console.log(444);// const cs1s = yield \'foo\';// console.log(cs1s); // pppppp// } catch (error) {//     console.log(error);// }}// 调用并不会立即执行,而只是生成了一个Generator空对象.const Generator = test(); // Object [Generator] {}/*** 只有调用Generator的next方法时才会执行并返回一个对象{value: undefined, done: true}* yield表示暂停并把返回值赋值给对象的value属性 return表示结束并把返回值赋值给对象的value属性* done表示代码是否全部执行完只有使用yield是done为false。* 下次调用next时从上次暂停处继续执行,直到下一处yield或return。* next方法接收一个参数,作为yield的返回值*/const aa = Generator.next();console.log(aa); // {value: \"foo\", done: false}const bb = Generator.next(\'pppppp\');console.log(bb); // {value: \"foo1111\", done: false}// 抛出一个错误可以在test内部捕获错误Generator.throw(\'error111\');

    15. Generator 异步方案(中)

    function ajax(url){return new Promise(function(resolve,reject){const xhr = new XMLHttpRequest();xhr.open(\'GET\',url);xhr.responseType = \'json\';xhr.onload = function(){if (this.status === 200){resolve(this.response)} else {reject(this.statusText)}}xhr.send()})}function * generat(){console.log(\'start\');const data = yield ajax(\'./data.json\');console.log(data);const data1 = yield ajax(\'./data1.json\');console.log(data1);}const generator = generat();const promise1 = generator.next();promise1.value.then(data => {if (promise1.done) returnconst promise2 = generator.next(data);promise2.value.then(data1 => {const promise3 = generator.next(data1);})})// const promise1 = generator.next();// promise1.value.then(data => {//     console.log(data);//     if (promise1.done) return// })// const promise2 = generator.next();// promise2.value.then(data => {//     console.log(data);//     if (promise2.done) return// })

    16. Generator 异步方案(下)

    这样异步函数就跟写同步函数很像了,但是他还是比较麻烦,因为要自己去创建一个生成器函数

    function ajax(url){return new Promise(function(resolve,reject){const xhr = new XMLHttpRequest();xhr.open(\'GET\',url);xhr.responseType = \'json\';xhr.onload = function(){if (this.status === 200){resolve(this.response)} else {reject(this.statusText)}}xhr.send()})}function * generat(){console.log(\'start\');const data = yield ajax(\'./data.json\');console.log(data);const data1 = yield ajax(\'./data1.json\');console.log(data1);}// 使用递归和生成器函数的方法处理异步函数// const generator = generat();// function handleResult (promise1){//     if (promise1.done) return // 生成器函数结束//     promise1.value.then(data => {//         handleResult(generator.next(data))//     }, err => {//         console.log(err);//     })// }// handleResult(generator.next())// 把使用递归和生成器函数进行封装function co (generat){const generator = generat();function handleResult (promise1){if (promise1.done) return // 生成器函数结束promise1.value.then(data => {handleResult(generator.next(data))}, err => {console.log(err);})}handleResult(generator.next())}co(generat)

    17. Async 函数

    function ajax(url){return new Promise(function(resolve,reject){const xhr = new XMLHttpRequest();xhr.open(\'GET\',url);xhr.responseType = \'json\';xhr.onload = function(){if (this.status === 200){resolve(this.response)} else {reject(this.statusText)}}xhr.send()})}// async/awaitasync function  generat(){console.log(\'start\');const data = await ajax(\'./data.json\');console.log(data);const data1 = await ajax(\'./data1.json\');console.log(data1);}generat()// async/await 和generator用法上很像// function * generat(){//     console.log(\'start\');//     const data = yield ajax(\'./data.json\');//     console.log(data);//     const data1 = yield ajax(\'./data1.json\');//     console.log(data1);// }

    18. 随堂测试

    任务三:手写 Promise 源码

    1. Promise 类核心逻辑实现

    myPromise.js

    const PENDING = \'pending\';const FULFILLED = \'fulfilled\';const REJECTED = \'rejected\';class MyPromise {// 构造函数constructor(executor) {// 传入的执行器函数,有两个函数作为参数并立即执行executor(this.resolve,this.reject);}// promise状态,默认为 pendingstatus = PENDING;// 成功之后的值,默认 undefinedvalue = undefined;// 失败后的值,默认 undefinedreason = undefined;// 成功的函数// 因为调用是直接调用的即resolve()所以要使用箭头函数,它的this才是指向这个类的实例对象也就是promise对象resolve = (value) => {// 如果状态等于 pending 就不执行if(this.status != PENDING) returnthis.value = value;this.status = FULFILLED;}// 失败的函数reject = (reason) => {// 如果状态等于 pending 就不执行if(this.status != PENDING) returnthis.reason = reason;this.status = REJECTED;}// then不是直接调用的,所以不需要使用箭头函数then(successfulCallBack, loseCallBack) {// 成功后调用成功的回调,并返回成功的数据if (this.status === FULFILLED) {successfulCallBack(this.value);// 失败后调用失败的回调,并返回失败的原因} else if (this.status === REJECTED) {loseCallBack(this.reason);}}// then = (successfulCallBack, loseCallBack) => {//   // 成功后调用成功的回调,并返回成功的数据//   if (this.status === FULFILLED) {//     successfulCallBack(this.value);//     // 失败后调用失败的回调,并返回失败的原因//   } else if (this.status === REJECTED) {//     loseCallBack(this.reason);//   }// }}module.exports = MyPromise

    test.js

    /*** Promise做了哪些事?* Promise其实是个类。首先传入了一个称之为执行器函数,这个执行器是立即执行的,他接受两个函数做参数第一个是成功时的函数,另一个是失败时的函数。* 它有三个状态 pending fulfilled rejected* pending:表示进行中的状态* fulfilled:表示成功后的状态* rejected:表示失败后的状态* 状态只能如下改变* pending ===》 fulfilled 或 pending ===》 rejected* then方法接收两个回调函数,第一个是成功的回调函数,第二个是失败的回调函数*/// Promise 类核心逻辑实现const MyPromise = require(\'./myPromise\')new MyPromise((resolve,reject) =>{resolve(\'成功\');// reject(\'失败\');}).then(data => {console.log(data); // 成功console.log(111);}, err => {console.log(err); // 失败console.log(222);})// 原生promise// const promise = new Promise((resolve,reject) =>{//     // resolve(\'成功\');//     reject(\'失败\');// }).then(data => {//     console.log(data);// }, err => {//     console.log(err);// })

    2. 在 Promise 类中加入异步逻辑

    test.js

    /*** 上一节只是同步代码的执行而没考虑到异步代码的执行,这次考虑异步*/// Promise 实现异步代码调用const MyPromise = require(\'./myPromise\')new MyPromise((resolve,reject) =>{// 这里使用了异步代码,不会马上执行,而是放到事件队列中去,但是下面的then会马上执行setTimeout(() => {// resolve(\'成功\');reject(\'失败\');}, 2000);}).then(data => {console.log(data); // 成功}, err => {console.log(err); // 失败})

    myPromise.js

    const PENDING = \'pending\';const FULFILLED = \'fulfilled\';const REJECTED = \'rejected\';class MyPromise {// 构造函数constructor(executor) {// 传入的执行器函数,有两个函数作为参数并立即执行executor(this.resolve,this.reject);}// promise状态,默认为 pendingstatus = PENDING;// 成功之后的值,默认 undefinedvalue = undefined;// 失败后的值,默认 undefinedreason = undefined;// 成功后的回调successfulCallBack = undefined;//   失败后的回调loseCallBack = undefined;// 成功的函数// 因为调用是直接调用的即resolve()所以要使用箭头函数,它的this才是指向这个类的实例对象也就是promise对象resolve = (value) => {// 如果状态等于 pending 就不执行if(this.status != PENDING) returnthis.value = value;this.status = FULFILLED;// 如果有值就执行这个成功的回调函数if (this.successfulCallBack) this.successfulCallBack(this.value);}// 失败的函数reject = (reason) => {// 如果状态等于 pending 就不执行if(this.status != PENDING) returnthis.reason = reason;this.status = REJECTED;// 如果有值就执行这个失败的回调函数if (this.loseCallBack) this.loseCallBack(this.reason);}// then不是直接调用的,所以不需要使用箭头函数then(successfulCallBack, loseCallBack) {// 成功后调用成功的回调,并返回成功的数据if (this.status === FULFILLED) {successfulCallBack(this.value);// 失败后调用失败的回调,并返回失败的原因} else if (this.status === REJECTED) {loseCallBack(this.reason);// 异步操作// 因为是异步操作所以当状态不确定前不知道该调用成功的还是失败的回调// 所以先把它保存到这个类的实例属性中,等确定了状态后再调用} else {this.successfulCallBack = successfulCallBack;this.loseCallBack = loseCallBack;}}// then = (successfulCallBack, loseCallBack) => {//   // 成功后调用成功的回调,并返回成功的数据//   if (this.status === FULFILLED) {//     successfulCallBack(this.value);//     // 失败后调用失败的回调,并返回失败的原因//   } else if (this.status === REJECTED) {//     loseCallBack(this.reason);//   }// }}module.exports = MyPromise

    3. 实现 then 方法多次调用添加多个处理函数

    myPromise.js

    const PENDING = \'pending\';const FULFILLED = \'fulfilled\';const REJECTED = \'rejected\';class MyPromise {constructor(executor) {executor(this.resolve,this.reject);}status = PENDING;value = undefined;reason = undefined;// 默认为空数组,因为有多个异步函数调用successfulCallBack = [];loseCallBack = [];resolve = (value) => {if(this.status != PENDING) returnthis.value = value;this.status = FULFILLED;// 当数组的长度等于0时停止循环。 shift会改变原数组,移除数组第一项并返回while (this.successfulCallBack.length) this.successfulCallBack.shift()(this.value);}reject = (reason) => {if(this.status != PENDING) returnthis.reason = reason;this.status = REJECTED;// 当数组的长度等于0时停止循环。 shift会改变原数组,移除数组第一项并返回while (this.loseCallBack.length) this.loseCallBack.shift()(this.reason);}then(successfulCallBack, loseCallBack) {if (this.status === FULFILLED) {successfulCallBack(this.value);} else if (this.status === REJECTED) {loseCallBack(this.reason);// 异步操作} else {// 把调用then传过来的回调函数都添加到数组中去,等到状态确定后在循环调用this.successfulCallBack.push(successfulCallBack);this.loseCallBack.push(loseCallBack);}}}module.exports = MyPromise

    test.js

    /*** 上一节虽然考虑到了异步函数,但是没考虑到多个调用*/// Promise 实现多个异步代码调用const MyPromise = require(\'./myPromise\')const promise = new MyPromise((resolve,reject) =>{// 这里使用了异步代码,不会马上执行,而是放到事件队列中去,但是下面的then会马上执行setTimeout(() => {resolve(\'成功\');// reject(\'失败\');}, 2000);})promise.then(data => {console.log(data); // 成功}, err => {console.log(err); // 失败})promise.then(data => {console.log(data); // 成功}, err => {console.log(err); // 失败})promise.then(data => {console.log(data); // 成功}, err => {console.log(err); // 失败})

    4. 实现 then 方法的链式调用(一)

    myPromise.js

    const PENDING = \'pending\';const FULFILLED = \'fulfilled\';const REJECTED = \'rejected\';class MyPromise {constructor(executor) {executor(this.resolve,this.reject);}status = PENDING;value = undefined;reason = undefined;successfulCallBack = [];loseCallBack = [];resolve = (value) => {if(this.status != PENDING) returnthis.value = value;this.status = FULFILLED;while (this.successfulCallBack.length) this.successfulCallBack.shift()(this.value);}reject = (reason) => {if(this.status != PENDING) returnthis.reason = reason;this.status = REJECTED;while (this.loseCallBack.length) this.loseCallBack.shift()(this.reason);}then(successfulCallBack, loseCallBack) {// 1.then方法要链式调用就必须要返回Promise对象,Promise接收一个执行器,这个执行器函数会立即执行const promise2 = new Promise((resolve,reject) => {if (this.status === FULFILLED) {// 3.又因为我们需要把上一个then方法返回的值传递给下一个then,这里的x就是上一个的返回值let x = successfulCallBack(this.value);resolve(x)// 4.既然要传递给下一个then那就调用resolve成功时的函数传递给下一个then了} else if (this.status === REJECTED) {let x = loseCallBack(this.reason);reject(x)// 异步操作} else {this.successfulCallBack.push(successfulCallBack);this.loseCallBack.push(loseCallBack);}});// 2.而这一段代码也是需要立即执行的,所以在创建一个新的Promise的时候需要把这一段代码放进去// if (this.status === FULFILLED) {//   successfulCallBack(this.value);// } else if (this.status === REJECTED) {//   loseCallBack(this.reason);// // 异步操作// } else {//     this.successfulCallBack.push(successfulCallBack);//     this.loseCallBack.push(loseCallBack);// }// 返回Promisereturn promise2}}module.exports = MyPromise

    test.js

    /*** 上一节虽然考虑到了异步函数,但是没考虑到多个调用*/// Promise 实现多个异步代码调用const MyPromise = require(\'./myPromise\')const promise = new MyPromise((resolve,reject) =>{// 这里使用了异步代码,不会马上执行,而是放到事件队列中去,但是下面的then会马上执行// setTimeout(() => {//     resolve(\'成功\');//     reject(\'失败\');// }, 2000);resolve(\'成功\');})promise.then(data => {console.log(data); // 成功return 100;}).then(data => {console.log(data); // 100promise.then(data => {console.log(data); // 成功return 100;},err => {console.log(4444);return \'shibai\'}).then(data => {console.log(data); // 100}, err => {console.log(err);})})// 1.then方法要链式调用就必须要返回Promise对象// 2.需要把上一个then的返回值传递给下一个then成功的回调

    5. 实现 then 方法的链式调用(二)

    test.js

    /*** 上一节虽然考虑到了异步代码的多个调用,但是没考虑到then方法的返回值得问题* 如果then返回Promise对象则调用相应回调,即这个Promise对象的状态是成功就调用成功的回调,状态是失败就调用失败的回调* 如果返回的不是Promise对象则调用对应的resolve或reject返回给下一个then*/const MyPromise = require(\'./myPromise\')const promise = new MyPromise((resolve,reject) =>{// 这里使用了异步代码,不会马上执行,而是放到事件队列中去,但是下面的then会马上执行// setTimeout(() => {//     resolve(\'成功\');//     reject(\'失败\');// }, 2000);resolve(\'成功\');// reject(\'失败\');})promise.then(data => {console.log(data,\'pppp\'); // 成功return other();}).then(data => {console.log(data, \'******\'); // other}).then(data => {// 这里打印undefined是因为上一个then没有返回值console.log(data, \'---------\'); // undefined})function other() {return new MyPromise((resolve, reject) => {resolve(\'other\');})}

    myPromise.js

    const PENDING = \'pending\';const FULFILLED = \'fulfilled\';const REJECTED = \'rejected\';class MyPromise {constructor(executor) {executor(this.resolve,this.reject);}status = PENDING;value = undefined;reason = undefined;successfulCallBack = [];loseCallBack = [];resolve = (value) => {if(this.status != PENDING) returnthis.value = value;this.status = FULFILLED;while (this.successfulCallBack.length) this.successfulCallBack.shift()(this.value);}reject = (reason) => {if(this.status != PENDING) returnthis.reason = reason;this.status = REJECTED;while (this.loseCallBack.length) this.loseCallBack.shift()(this.reason);}then(successfulCallBack, loseCallBack) {// 1.then方法要链式调用就必须要返回Promise对象,Promise接收一个执行器,这个执行器函数会立即执行const promise2 = new Promise((resolve,reject) => {if (this.status === FULFILLED) {// 3.又因为我们需要把上一个then方法返回的值传递给下一个then,这里的x就是上一个的返回值let x = successfulCallBack(this.value);/*** 如果then返回Promise对象则调用相应回调,即这个Promise对象的状态是成功就调用成功的回调,状态是失败就调用失败的回调* 如果返回的不是Promise对象则调用对应的resolve或reject返回给下一个then*/resolvePromise(x,resolve,reject)// resolve(x)// 4.既然要传递给下一个then那就调用resolve成功时的函数传递给下一个then了} else if (this.status === REJECTED) {let x = loseCallBack(this.reason);reject(x)// 异步操作} else {this.successfulCallBack.push(successfulCallBack);this.loseCallBack.push(loseCallBack);}});return promise2}}function resolvePromise(x,resolve,reject) {// 是Promise对象  instanceof 用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链if (x instanceof MyPromise) {// 调用返回的这个promise的then方法去查看这个promise的状态,如果成功的状态则调用resolve,失败就调用reject// x.then((value) => resolve(value), (err) => reject(err))// 简写x.then(resolve,reject);} else {// 如果不是Promise对象则直接调用resolve(x)}}module.exports = MyPromise

    6. then 方法链式调用识别 Promise 对象自返回

    test.js

    const MyPromise = require(\'./myPromise\')// 原生Promise中如果调用then方法后再返回自己,则会报错 Chaining cycle detected for promise #<Promise></Promise>// const promise = new Promise(function(resolve,reject) {//     resolve(\'dd\')// })// var p1 = promise.then(data => {//     return p1// }, err => {//     // console.log(err);// })// console.log(p1,\'xxxxx\');// 自己实现的捕获调用then方法后再返回自己的错误const promise1 = new MyPromise(function(resolve,reject) {resolve(\'dd\')})// 这里调用Promise的then方法后又返回了自己p1var p1 = promise1.then(data => {return p1}, err => {console.log(err.message);})// console.log(p1,\'xxxxx\');

    myPromise.js

    const PENDING = \'pending\';const FULFILLED = \'fulfilled\';const REJECTED = \'rejected\';class MyPromise {constructor(executor) {executor(this.resolve,this.reject);}status = PENDING;value = undefined;reason = undefined;successfulCallBack = [];loseCallBack = [];resolve = (value) => {if(this.status != PENDING) returnthis.value = value;this.status = FULFILLED;while (this.successfulCallBack.length) this.successfulCallBack.shift()(this.value);}reject = (reason) => {if(this.status != PENDING) returnthis.reason = reason;this.status = REJECTED;while (this.loseCallBack.length) this.loseCallBack.shift()(this.reason);}then(successfulCallBack, loseCallBack) {// promise2就是test.js中的var p1中的那个Promise对象,而这里的x就是test.js中return返回的那个值// 所以只要对比下就知道它们是不是同一个Promise是的话就直接报错,把判断逻辑放到resolvePromise中,因为这个很多地方需要用到const promise2 = new Promise((resolve,reject) => {if (this.status === FULFILLED) {// 错误代码。但是在new Promise是不能直接拿到promise2的值的,因为promise2是new Promise执行完的结果// 要想拿到就可以使用异步调用,等new Promise执行完后在取就可以了// let x = successfulCallBack(this.value);// resolvePromise(promise2,x,resolve,reject)setTimeout(() => {let x = successfulCallBack(this.value);resolvePromise(promise2,x,resolve,reject)}, 0);} else if (this.status === REJECTED) {let x = loseCallBack(this.reason);reject(x)// 异步操作} else {this.successfulCallBack.push(successfulCallBack);this.loseCallBack.push(loseCallBack);}});return promise2}}/*** 如果then返回Promise对象则调用相应回调,即这个Promise对象的状态是成功就调用成功的回调,状态是失败就调用失败的回调* 如果返回的不是Promise对象则调用对应的resolve或reject返回给下一个then*/function resolvePromise(promise2,x,resolve,reject) {if (promise2 === x) {reject(new TypeError(\"Chaining cycle detected for promise #<Promise>\"))}// 是Promise对象  instanceof 用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链if (x instanceof MyPromise) {// 调用返回的这个promise的then方法去查看这个promise的状态,如果成功的状态则调用resolve,失败就调用reject// x.then((value) => resolve(value), (err) => reject(err))// 简写x.then(resolve,reject);} else {// 如果不是Promise对象则直接调用resolve(x)}}module.exports = MyPromise

    7. 捕获错误及 then 链式调用其他状态代码补充

    test.js

    const MyPromise = require(\'./myPromise\')// const promise1 = new MyPromise(function(resolve,reject) {//     // 1.执行器中手动抛出一个错误//     throw new Error(\'shib111\')//     resolve(\'dd\')// })// var p1 = promise1.then(data => {//     // 2.在成功函数的抛出错误,会在下一个then方法的失败回调中报错错误//     // throw new Error(\'成功回调抛出的错误\')//     resolve(\'dd\')// }, err => {//     console.log(err,\'llllll\'); // Error: shib111// })// .then(data => {//     console.log(data);// }, err => {//     console.log(err,\'ppppp\'); // Error: 成功回调抛出的错误// })const promise2 = new MyPromise(function(resolve,reject) {// 3.执行器中异步操作手动抛出一个错误setTimeout(() => {resolve(\'成功\');}, 2000);})promise2.then(data => {console.log(data,\'[[[\'); // 成功return \'aaaa\'},err => {console.log(err,\'ppppp\');return 10000}).then(data => {console.log(data,\'sss\'); // aaaa}, err => {console.log(err,\'lll\');})

    myPromise.js

    const PENDING = \'pending\';const FULFILLED = \'fulfilled\';const REJECTED = \'rejected\';class MyPromise {constructor(executor) {// 1.捕捉执行器抛出的错误try {executor(this.resolve,this.reject);} catch (error) {this.reject(error);}}status = PENDING;value = undefined;reason = undefined;successfulCallBack = [];loseCallBack = [];resolve = (value) => {if(this.status != PENDING) returnthis.value = value;this.status = FULFILLED;while (this.successfulCallBack.length) this.successfulCallBack.shift()();}reject = (reason) => {if(this.status != PENDING) returnthis.reason = reason;this.status = REJECTED;while (this.loseCallBack.length) this.loseCallBack.shift()();}then(successfulCallBack, loseCallBack) {const promise2 = new Promise((resolve,reject) => {if (this.status === FULFILLED) {// 2.捕捉成功函数的错误setTimeout(() => {try {let x = successfulCallBack(this.value);resolvePromise(promise2,x,resolve,reject)} catch (error) {reject(error);}}, 0);} else if (this.status === REJECTED) {let x = loseCallBack(this.reason);reject(x)// 3.捕捉失败函数的错误setTimeout(() => {try {let x = loseCallBack(this.reason);resolvePromise(promise2,x,resolve,reject)} catch (error) {reject(error);}}, 0);// 异步操作} else {// 4.在异步操作中捕获错误// 未捕获前的代码// this.successfulCallBack.push(successfulCallBack);// this.loseCallBack.push(loseCallBack);// 修改后的代码// 直接传递一个参数无法处理数据,所以返回一个函数this.successfulCallBack.push(() => {setTimeout(() => {try {let x = successfulCallBack(this.value);resolvePromise(promise2,x,resolve,reject)} catch (error) {reject(error);}}, 0);});this.loseCallBack.push(() => {setTimeout(() => {try {let x = loseCallBack(this.reason);resolvePromise(promise2,x,resolve,reject)} catch (error) {reject(error);}}, 0);});}});return promise2}}/*** 如果then返回Promise对象则调用相应回调,即这个Promise对象的状态是成功就调用成功的回调,状态是失败就调用失败的回调* 如果返回的不是Promise对象则调用对应的resolve或reject返回给下一个then*/function resolvePromise(promise2,x,resolve,reject) {if (promise2 === x) {reject(new TypeError(\"Chaining cycle detected for promise #<Promise>\"))}// 是Promise对象  instanceof 用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链if (x instanceof MyPromise) {// 调用返回的这个promise的then方法去查看这个promise的状态,如果成功的状态则调用resolve,失败就调用reject// x.then((value) => resolve(value), (err) => reject(err))// 简写x.then(resolve,reject);} else {// 如果不是Promise对象则直接调用resolve(x)}}module.exports = MyPromise

    8. 将 then 方法的参数变成可选参数

    test.js

    /*** 将then的参数变成可选参数*/// 原生Promise中then方法不传递也会返回调用resolve或reject时返回的值const promise = new Promise(function(resolve,reject) {resolve(\'成功\')})promise.then().then().then(data => {console.log(data); // 成功})// 自己实现这么效果const MyPromise = require(\'./myPromise\')const p1 = new MyPromise(function(reslove,reject) {// reslove(\'成功了\')reject(\'失败了\')})p1.then().then().then(data => {console.log(data); // 成功了},err => {console.log(err); // 失败了})

    myPromise.js

    const PENDING = \'pending\';const FULFILLED = \'fulfilled\';const REJECTED = \'rejected\';class MyPromise {constructor(executor) {// 1.捕捉执行器抛出的错误try {executor(this.resolve,this.reject);} catch (error) {this.reject(error);}}status = PENDING;value = undefined;reason = undefined;successfulCallBack = [];loseCallBack = [];resolve = (value) => {if(this.status != PENDING) returnthis.value = value;this.status = FULFILLED;while (this.successfulCallBack.length) this.successfulCallBack.shift()();}reject = (reason) => {if(this.status != PENDING) returnthis.reason = reason;this.status = REJECTED;while (this.loseCallBack.length) this.loseCallBack.shift()();}then(successfulCallBack, loseCallBack) {// 当调用then方法时没有传入参数那么我们就默认给他一个函数作为参数,这个函数就把resolve或resolve传入的结果直接返回给下一个thensuccessfulCallBack = successfulCallBack ? successfulCallBack : (val) => valloseCallBack = loseCallBack ? loseCallBack : (rea) => reaconst promise2 = new Promise((resolve,reject) => {if (this.status === FULFILLED) {// 2.捕捉成功函数的错误setTimeout(() => {try {let x = successfulCallBack(this.value);resolvePromise(promise2,x,resolve,reject)} catch (error) {reject(error);}}, 0);} else if (this.status === REJECTED) {let x = loseCallBack(this.reason);reject(x)// 3.捕捉失败函数的错误setTimeout(() => {try {let x = loseCallBack(this.reason);resolvePromise(promise2,x,resolve,reject)} catch (error) {reject(error);}}, 0);// 异步操作} else {// 4.在异步操作中捕获错误// 未捕获前的代码// this.successfulCallBack.push(successfulCallBack);// this.loseCallBack.push(loseCallBack);// 修改后的代码// 直接传递一个参数无法处理数据,所以返回一个函数this.successfulCallBack.push(() => {setTimeout(() => {try {let x = successfulCallBack(this.value);resolvePromise(promise2,x,resolve,reject)} catch (error) {reject(error);}}, 0);});this.loseCallBack.push(() => {setTimeout(() => {try {let x = loseCallBack(this.reason);resolvePromise(promise2,x,resolve,reject)} catch (error) {reject(error);}}, 0);});}});return promise2}}/*** 如果then返回Promise对象则调用相应回调,即这个Promise对象的状态是成功就调用成功的回调,状态是失败就调用失败的回调* 如果返回的不是Promise对象则调用对应的resolve或reject返回给下一个then*/function resolvePromise(promise2,x,resolve,reject) {if (promise2 === x) {reject(new TypeError(\"Chaining cycle detected for promise #<Promise>\"))}// 是Promise对象  instanceof 用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链if (x instanceof MyPromise) {// 调用返回的这个promise的then方法去查看这个promise的状态,如果成功的状态则调用resolve,失败就调用reject// x.then((value) => resolve(value), (err) => reject(err))// 简写x.then(resolve,reject);} else {// 如果不是Promise对象则直接调用resolve(x)}}module.exports = MyPromise

    9. Promise.all 方法的实现

    test.js

    /*** all可以让异步代码调用的顺序得到异步代码执行得到的结果* 利用类点一个方法名则一定是个静态方法例如promise.then中的promise是类Promise的实例* 而all方法的调用是Promise.all直接用类进行调用,所以all方法就是一个静态的方法* all返回一个promise对象* all接收一个数组,这个数组每一项可以是promise对象或普通值* all方法会按这个数组参数来依次执行并依次返回执行结果,不管它是不是异步调用* all方法只要有一个调用失败即停止执行返回失败状态*/// 自己实现这么效果const MyPromise = require(\'./myPromise\')const p1 = new MyPromise(function(reslove,reject) {setTimeout(() => {reslove(\'成功了\')}, 2000);})const p2 = new MyPromise(function(reslove,reject) {reslove(\'成功了哦哦哦哦哦哦\')})MyPromise.all([\'1\',\'5\',p1,\'8\',p2]).then(value => {// 打印结果是[ \'1\', \'5\', <1 empty item>, \'8\', \'成功了哦哦哦哦哦哦\' ]// <1 empty item>原本应该是p1和p2的返回值,但是却返回的是空的// 原因就是myPromise.js中的静态方法all中有说明console.log(value); // 正确结果是[ \'1\', \'5\', \'成功了\', \'8\', \'成功了哦哦哦哦哦哦\' ]},err => {console.log(err);})

    myPromise.js

    const PENDING = \'pending\';const FULFILLED = \'fulfilled\';const REJECTED = \'rejected\';class MyPromise {constructor(executor) {// 1.捕捉执行器抛出的错误try {executor(this.resolve,this.reject);} catch (error) {this.reject(error);}}status = PENDING;value = undefined;reason = undefined;successfulCallBack = [];loseCallBack = [];resolve = (value) => {if(this.status != PENDING) returnthis.value = value;this.status = FULFILLED;while (this.successfulCallBack.length) this.successfulCallBack.shift()();}reject = (reason) => {if(this.status != PENDING) returnthis.reason = reason;this.status = REJECTED;while (this.loseCallBack.length) this.loseCallBack.shift()();}static all(array) {// 返回的数组let result = [];// 用于判断何时执行addData中resolve的调用时机let index = 0;return new MyPromise(function(resolve,reject) {// 传入索引是为了返回的结果也是按原顺序返回function addData(key, val) {index ++;result[key] = val;if (index  === array.length) {resolve(result);}}for (let i = 0; i < array.length; i++) {const element = array[i];if (element instanceof MyPromise) {// 是promise对象,需要调用这个promise的then方法查看他的状态,成功状态则继续添加进要返回的数组中,失败的状态就直接调用reject返回错误信息element.then(value => {addData(i,value)}, reason => reject(reason))} else {// 不是promise对象则直接将结果添加到返回的那个数组中addData(i,element)}}// 因为for循环是同步代码,而array中的每一项有可能是一个异步执行的代码,所以for循环执行完,但是因为其中有些项是异步所以会先会继续执行resolve方法,// 所以此时result是未添加异步代码执行后返回的结果的,可以把它写在addData中因为addData就是在异步中调用的,// 但是这样的话每循环依次就会执行一次resolve是不行的,所以加个index作为标识//// resolve(result)})}then(successfulCallBack, loseCallBack) {// 当调用then方法时没有传入参数那么我们就默认给他一个函数作为参数,这个函数就把resolve或resolve传入的结果直接返回给下一个thensuccessfulCallBack = successfulCallBack ? successfulCallBack : (val) => valloseCallBack = loseCallBack ? loseCallBack : (rea) => reaconst promise2 = new Promise((resolve,reject) => {if (this.status === FULFILLED) {// 2.捕捉成功函数的错误setTimeout(() => {try {let x = successfulCallBack(this.value);resolvePromise(promise2,x,resolve,reject)} catch (error) {reject(error);}}, 0);} else if (this.status === REJECTED) {let x = loseCallBack(this.reason);reject(x)// 3.捕捉失败函数的错误setTimeout(() => {try {let x = loseCallBack(this.reason);resolvePromise(promise2,x,resolve,reject)} catch (error) {reject(error);}}, 0);// 异步操作} else {// 4.在异步操作中捕获错误// 未捕获前的代码// this.successfulCallBack.push(successfulCallBack);// this.loseCallBack.push(loseCallBack);// 修改后的代码// 直接传递一个参数无法处理数据,所以返回一个函数this.successfulCallBack.push(() => {setTimeout(() => {try {let x = successfulCallBack(this.value);resolvePromise(promise2,x,resolve,reject)} catch (error) {reject(error);}}, 0);});this.loseCallBack.push(() => {setTimeout(() => {try {let x = loseCallBack(this.reason);resolvePromise(promise2,x,resolve,reject)} catch (error) {reject(error);}}, 0);});}});return promise2}}/*** 如果then返回Promise对象则调用相应回调,即这个Promise对象的状态是成功就调用成功的回调,状态是失败就调用失败的回调* 如果返回的不是Promise对象则调用对应的resolve或reject返回给下一个then*/function resolvePromise(promise2,x,resolve,reject) {if (promise2 === x) {reject(new TypeError(\"Chaining cycle detected for promise #<Promise>\"))}// 是Promise对象  instanceof 用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链if (x instanceof MyPromise) {// 调用返回的这个promise的then方法去查看这个promise的状态,如果成功的状态则调用resolve,失败就调用reject// x.then((value) => resolve(value), (err) => reject(err))// 简写x.then(resolve,reject);} else {// 如果不是Promise对象则直接调用resolve(x)}}module.exports = MyPromise

    10. Promise.resolve 方法的实现

    test.js

    /*** Promise.resolve把传过去的值变成promise对象并返回* Promise.resolve方法接收一个参数,如果这个参数不是promise对象的话就会包装promise对象返回,如果参数是promise对象的话直接返回,*/// 原生PromisePromise.resolve(1000).then(value => console.log(value))// 自己实现这个效果// 是类直接调用所以是静态方法const MyPromise = require(\'./myPromise\')MyPromise.resolve(2000).then(value => console.log(value))

    myPromise.js

    const PENDING = \'pending\';const FULFILLED = \'fulfilled\';const REJECTED = \'rejected\';class MyPromise {constructor(executor) {4000// 1.捕捉执行器抛出的错误try {executor(this.resolve,this.reject);} catch (error) {this.reject(error);}}status = PENDING;value = undefined;reason = undefined;successfulCallBack = [];loseCallBack = [];resolve = (value) => {if(this.status != PENDING) returnthis.value = value;this.status = FULFILLED;while (this.successfulCallBack.length) this.successfulCallBack.shift()();}reject = (reason) => {if(this.status != PENDING) returnthis.reason = reason;this.status = REJECTED;while (this.loseCallBack.length) this.loseCallBack.shift()();}static resolve(val) {// 如果是MyPromise的实例对象就说明是promise对象直接返回if (val instanceof MyPromise) return val// 不是返回一个promise对象并把传过来的值通过reslove返回出去return new MyPromise(function(reslove,reject) {reslove(val)})}static all(array) {// 返回的数组let result = [];// 用于判断何时执行addData中resolve的调用时机let index = 0;return new MyPromise(function(resolve,reject) {// 传入索引是为了返回的结果也是按原顺序返回function addData(key, val) {index ++;result[key] = val;if (index  === array.length) {resolve(result);}}for (let i = 0; i < array.length; i++) {const element = array[i];if (element instanceof MyPromise) {// 是promise对象,需要调用这个promise的then方法查看他的状态,成功状态则继续添加进要返回的数组中,失败的状态就直接调用reject返回错误信息element.then(value => {addData(i,value)}, reason => reject(reason))} else {// 不是promise对象则直接将结果添加到返回的那个数组中addData(i,element)}}// 因为for循环是同步代码,而array中的每一项有可能是一个异步执行的代码,所以for循环执行完,但是因为其中有些项是异步所以会先会继续执行resolve方法,// 所以此时result是未添加异步代码执行后返回的结果的,可以把它写在addData中因为addData就是在异步中调用的,// 但是这样的话每循环依次就会执行一次resolve是不行的,所以加个index作为标识//// resolve(result)})}then(successfulCallBack, loseCallBack) {// 当调用then方法时没有传入参数那么我们就默认给他一个函数作为参数,这个函数就把resolve或resolve传入的结果直接返回给下一个thensuccessfulCallBack = successfulCallBack ? successfulCallBack : (val) => valloseCallBack = loseCallBack ? loseCallBack : (rea) => reaconst promise2 = new Promise((resolve,reject) => {if (this.status === FULFILLED) {// 2.捕捉成功函数的错误setTimeout(() => {try {let x = successfulCallBack(this.value);resolvePromise(promise2,x,resolve,reject)} catch (error) {reject(error);}}, 0);} else if (this.status === REJECTED) {let x = loseCallBack(this.reason);reject(x)// 3.捕捉失败函数的错误setTimeout(() => {try {let x = loseCallBack(this.reason);resolvePromise(promise2,x,resolve,reject)} catch (error) {reject(error);}}, 0);// 异步操作} else {// 4.在异步操作中捕获错误// 未捕获前的代码// this.successfulCallBack.push(successfulCallBack);// this.loseCallBack.push(loseCallBack);// 修改后的代码// 直接传递一个参数无法处理数据,所以返回一个函数this.successfulCallBack.push(() => {setTimeout(() => {try {let x = successfulCallBack(this.value);resolvePromise(promise2,x,resolve,reject)} catch (error) {reject(error);}}, 0);});this.loseCallBack.push(() => {setTimeout(() => {try {let x = loseCallBack(this.reason);resolvePromise(promise2,x,resolve,reject)} catch (error) {reject(error);}}, 0);});}});return promise2}}/*** 如果then返回Promise对象则调用相应回调,即这个Promise对象的状态是成功就调用成功的回调,状态是失败就调用失败的回调* 如果返回的不是Promise对象则调用对应的resolve或reject返回给下一个then*/function resolvePromise(promise2,x,resolve,reject) {if (promise2 === x) {reject(new TypeError(\"Chaining cycle detected for promise #<Promise>\"))}// 是Promise对象  instanceof 用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链if (x instanceof MyPromise) {// 调用返回的这个promise的then方法去查看这个promise的状态,如果成功的状态则调用resolve,失败就调用reject// x.then((value) => resolve(value), (err) => reject(err))// 简写x.then(resolve,reject);} else {// 如果不是Promise对象则直接调用resolve(x)}}module.exports = MyPromise

    11. finally 方法的实现

    myPromise.js

    const PENDING = \'pending\';const FULFILLED = \'fulfilled\';const REJECTED = \'rejected\';class MyPromise {constructor(executor) {// 1.捕捉执行器抛出的错误try {executor(this.resolve,this.reject);} catch (error) {this.reject(error);}}status = PENDING;value = undefined;reason = undefined;successfulCallBack = [];loseCallBack = [];resolve = (value) => {if(this.status != PENDING) returnthis.value = value;this.status = FULFILLED;while (this.successfulCallBack.length) this.successfulCallBack.shift()();}reject = (reason) => {if(this.status != PENDING) returnthis.reason = reason;this.status = REJECTED;while (this.loseCallBack.length) this.loseCallBack.shift()();}// 不管失败和成功都会执行,因为finally后面还可以使用then方法所以要返回一个promise对象,// 可以通过调用then而拿到返回值finally(callback) {// 这里返回的是一个promise对象return this.then(value => {// 如果test.js中的finally参数内部手动return,那他的返回值的结果就是callback() === test.js中return的那个p1// 调用.then(() => value)是为了给finally后面的then返回值,即myPromise中reslove或reject的值,// 而并不是return的那个p1promise中的reslove或reject的值return MyPromise.resolve(callback()).then(() => value)},reason => {return MyPromise.resolve(callback()).then(() => reason)})}static resolve(val) {// 如果是MyPromise的实例对象就说明是promise对象直接返回if (val instanceof MyPromise) return val// 不是返回一个promise对象并把传过来的值通过reslove返回出去return new MyPromise(function(reslove,reject) {reslove(val)})}static all(array) {// 返回的数组let result = [];// 用于判断何时执行addData中resolve的调用时机let index = 0;return new MyPromise(function(resolve,reject) {// 传入索引是为了返回的结果也是按原顺序返回function addData(key, val) {index ++;result[key] = val;if (index  === array.length) {resolve(result);}}for (let i = 0; i < array.length; i++) {const element = array[i];if (element instanceof MyPromise) {// 是promise对象,需要调用这个promise的then方法查看他的状态,成功状态则继续添加进要返回的数组中,失败的状态就直接调用reject返回错误信息element.then(value => {addData(i,value)}, reason => reject(reason))} else {// 不是promise对象则直接将结果添加到返回的那个数组中addData(i,element)}}// 因为for循环是同步代码,而array中的每一项有可能是一个异步执行的代码,所以for循环执行完,但是因为其中有些项是异步所以会先会继续执行resolve方法,// 所以此时result是未添加异步代码执行后返回的结果的,可以把它写在addData中因为addData就是在异步中调用的,// 但是这样的话每循环依次就会执行一次resolve是不行的,所以加个index作为标识//// resolve(result)})}then(successfulCallBack, loseCallBack) {// 当调用then方法时没有传入参数那么我们就默认给他一个函数作为参数,这个函数就把resolve或resolve传入的结果直接返回给下一个thensuccessfulCallBack = successfulCallBack ? successfulCallBack : (val) => valloseCallBack = loseCallBack ? loseCallBack : (rea) => reaconst promise2 = new Promise((resolve,reject) => {if (this.status === FULFILLED) {// 2.捕捉成功函数的错误setTimeout(() => {try {let x = successfulCallBack(this.value);resolvePromise(promise2,x,resolve,reject)} catch (error) {reject(error);}}, 0);} else if (this.status === REJECTED) {let x = loseCallBack(this.reason);reject(x)// 3.捕捉失败函数的错误setTimeout(() => {try {let x = loseCallBack(this.reason);resolvePromise(promise2,x,resolve,reject)} catch (error) {reject(error);}}, 0);// 异步操作} else {// 4.在异步操作中捕获错误// 未捕获前的代码// this.successfulCallBack.push(successfulCallBack);// this.loseCallBack.push(loseCallBack);// 修改后的代码// 直接传递一个参数无法处理数据,所以返回一个函数this.successfulCallBack.push(() => {setTimeout(() => {try {let x = successfulCallBack(this.value);resolvePromise(promise2,x,resolve,reject)} catch (error) {reject(error);}}, 0);});this.loseCallBack.push(() => {setTimeout(() => {try {let x = loseCallBack(this.reason);resolvePromise(promise2,x,resolve,reject)} catch (error) {reject(error);}}, 0);});}});return promise2}}/*** 如果then返回Promise对象则调用相应回调,即这个Promise对象的状态是成功就调用成功的回调,状态是失败就调用失败的回调* 如果返回的不是Promise对象则调用对应的resolve或reject返回给下一个then*/function resolvePromise(promise2,x,resolve,reject) {if (promise2 === x) {reject(new TypeError(\"Chaining cycle detected for promise #<Promise>\"))}// 是Promise对象  instanceof 用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链if (x instanceof MyPromise) {// 调用返回的这个promise的then方法去查看这个promise的状态,如果成功的状态则调用resolve,失败就调用reject// x.then((value) => resolve(value), (err) => reject(err))// 简写x.then(resolve,reject);} else {// 如果不是Promise对象则直接调用resolve(x)}}module.exports = MyPromise

    test.js

    /*** finally方法* 无论promise执行的结果是成功还是失败,都会执行一次* finally方法后面的then方法会接受到finally方法里面的值* 在finally的 return返回的值并不会传递给下一个then方法的回调中去* 在finally的 return一个异步代码需等待异步执行完后再返回* 在then的 return返回的值会传递给下一个then方法的回调中去*/// 原生Promise// const promise = new Promise(function(resolve, reject) {//     resolve(\'成功啦\')//     // reject(\'失败啦\')// })// const p1 = new Promise(function(resolve, reject) {//     setTimeout(() => {//         resolve(\'迟到成功啦\')//     }, 2000);// })// promise.finally(value => {//     console.log(\'不管失败还是成功我都执行的呀\');//     // 在finally return返回的值并不会传递给下一个then方法的回调中去//     return p1// }).then(value => {//     console.log(value,\'vallll\');//     // 在then return返回的值会传递给下一个then方法的回调中去//     return \'niii\'// },err => {//     console.log(err,\'errr\');// }).then(value => {//     console.log(value,\'aaaaa\');// },err => {//     console.log(err,\'errr\');// })// 自己实现这个效果// 是类直接调用所以是静态方法const MyPromise = require(\'./myPromise\');const myPromise = new MyPromise(function(resolve,reject) {resolve(\'是成功哦\')// reject(\'是失败哦\')})// 如果在finally中返回一个异步的promise,则需要等待这个异步执行完再返回并且需要把这个传递给下个then的对应回调中const p1 = new MyPromise(function(resolve,reject) {setTimeout(() => {resolve(\'迟到的成功哦\')}, 2000);})myPromise.finally(val => {console.log(\'不管失败还是成功我都执行的呀....\');// 在finally return返回的值并不会传递给下一个then方法的回调中去return p1}).then(data => {console.log(data); // 等待2秒后打印  是成功哦// 在then return返回的值会传递给下一个then方法的回调中去// return \'niii\'}, err => {console.log(err);})

    12. catch 方法的实现

    test.js

    /*** catch方法的实现* 当调用then方法而不传递第二个参数的话,原生promise默认是传入undefined的,那then的异常信息就可以在catch方法中捕获到* catch内部调用的也是then方法只不过没有注册成功的回调而是注册失败的回调*/// 原生Promise// const promise = new Promise(function(resolve, reject) {//     reject(\'失败啦\')// })// promise// .then(value => {//     console.log(value,\'vallll\');// }).catch(err => {//     console.log(err,\'aaaaa\');// })// 自己实现这个效果// 是类直接调用所以是静态方法const MyPromise = require(\'./myPromise\');const myPromise = new MyPromise(function(resolve,reject) {reject(\'是失败哦\')})myPromise.then(value => {console.log(value,\'vallll\');}).catch(err => {console.log(err,\'aaaaa\'); // 是失败哦})

    myPromise.js

    const PENDING = \'pending\';const FULFILLED = \'fulfilled\';const REJECTED = \'rejected\';class MyPromise {constructor(executor) {// 1.捕捉执行器抛出的错误try {executor(this.resolve,this.reject);} catch (error) {this.reject(error);}}status = PENDING;value = undefined;reason = undefined;successfulCallBack = [];loseCallBack = [];resolve = (value) => {if(this.status != PENDING) returnthis.value = value;this.status = FULFILLED;while13b6a(this.successfulCallBack.length) this.successfulCallBack.shift()();}reject = (reason) => {if(this.status != PENDING) returnthis.reason = reason;this.status = REJECTED;while (this.loseCallBack.length) this.loseCallBack.shift()();}// 不管失败和成功都会执行,因为finally后面还可以使用then方法所以要返回一个promise对象,// 可以通过调用then而拿到返回值finally(callback) {// 这里返回的是一个promise对象return this.then(value => {// 如果test.js中的finally参数内部手动return,那他的返回值的结果就是callback() === test.js中return的那个p1// 调用.then(() => value)是为了给finally后面的then返回值,即myPromise中reslove或reject的值,// 而并不是return的那个p1promise中的reslove或reject的值return MyPromise.resolve(callback()).then(() => value)},reason => {return MyPromise.resolve(callback()).then(() => reason)})}static resolve(val) {// 如果是MyPromise的实例对象就说明是promise对象直接返回if (val instanceof MyPromise) return val// 不是返回一个promise对象并把传过来的值通过reslove返回出去return new MyPromise(function(reslove,reject) {reslove(val)})}static all(array) {// 返回的数组let result = [];// 用于判断何时执行addData中resolve的调用时机let index = 0;return new MyPromise(function(resolve,reject) {// 传入索引是为了返回的结果也是按原顺序返回function addData(key, val) {index ++;result[key] = val;if (index  === array.length) {resolve(result);}}for (let i = 0; i < array.length; i++) {const element = array[i];if (element instanceof MyPromise) {// 是promise对象,需要调用这个promise的then方法查看他的状态,成功状态则继续添加进要返回的数组中,失败的状态就直接调用reject返回错误信息element.then(value => {addData(i,value)}, reason => reject(reason))} else {// 不是promise对象则直接将结果添加到返回的那个数组中addData(i,element)}}// 因为for循环是同步代码,而array中的每一项有可能是一个异步执行的代码,所以for循环执行完,但是因为其中有些项是异步所以会先会继续执行resolve方法,// 所以此时result是未添加异步代码执行后返回的结果的,可以把它写在addData中因为addData就是在异步中调用的,// 但是这样的话每循环依次就会执行一次resolve是不行的,所以加个index作为标识//// resolve(result)})}// catch内部调用的也是then方法只不过没有注册成功的回调而是注册失败的回调catch(callback) {return this.then(undefined,callback)}then(successfulCallBack, loseCallBack) {// 当调用then方法时没有传入参数那么我们就默认给他一个函数作为参数,这个函数就把resolve或resolve传入的结果直接返回给下一个thensuccessfulCallBack = successfulCallBack ? successfulCallBack : (val) => valloseCallBack = loseCallBack ? loseCallBack : (rea) => reaconst promise2 = new Promise((resolve,reject) => {if (this.status === FULFILLED) {// 2.捕捉成功函数的错误setTimeout(() => {try {let x = successfulCallBack(this.value);resolvePromise(promise2,x,resolve,reject)} catch (error) {reject(error);}}, 0);} else if (this.status === REJECTED) {let x = loseCallBack(this.reason);reject(x)// 3.捕捉失败函数的错误setTimeout(() => {try {let x = loseCallBack(this.reason);resolvePromise(promise2,x,resolve,reject)} catch (error) {reject(error);}}, 0);// 异步操作} else {// 4.在异步操作中捕获错误// 未捕获前的代码// this.successfulCallBack.push(successfulCallBack);// this.loseCallBack.push(loseCallBack);// 修改后的代码// 直接传递一个参数无法处理数据,所以返回一个函数this.successfulCallBack.push(() => {setTimeout(() => {try {let x = successfulCallBack(this.value);resolvePromise(promise2,x,resolve,reject)} catch (error) {reject(error);}}, 0);});this.loseCallBack.push(() => {setTimeout(() => {try {let x = loseCallBack(this.reason);resolvePromise(promise2,x,resolve,reject)} catch (error) {reject(error);}}, 0);});}});return promise2}}/*** 如果then返回Promise对象则调用相应回调,即这个Promise对象的状态是成功就调用成功的回调,状态是失败就调用失败的回调* 如果返回的不是Promise对象则调用对应的resolve或reject返回给下一个then*/function resolvePromise(promise2,x,resolve,reject) {if (promise2 === x) {reject(new TypeError(\"Chaining cycle detected for promise #<Promise>\"))}// 是Promise对象  instanceof 用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链if (x instanceof MyPromise) {// 调用返回的这个promise的then方法去查看这个promise的状态,如果成功的状态则调用resolve,失败就调用reject// x.then((value) => resolve(value), (err) => reject(err))// 简写x.then(resolve,reject);} else {// 如果不是Promise对象则直接调用resolve(x)}}module.exports = MyPromise
    赞(0) 打赏
    未经允许不得转载:爱站程序员基地 » Part 1 · JavaScript深度剖析模块一:函数式编程与 JS 异步编程、手写 Promise