函数式编程-JS异步编程-手写promise

205 阅读7分钟

前端通识

  1. node和npm版本要求:
    • node版本建议最新版本,目前14.xx
    • npm版本小于(如果npm大于7,npm install npm@6.14.0 -g)
  2. 避免中文
  3. 不是内部或外部命令
    • 新建一个文件夹aaa,里面放一个可执行文件,比如xxx.bat(echo iloveu); 当前路径(aaa), cmd,输入可执行文件名称,自动执行。与aaa并列的bbb文件夹是空,执行xxx,报错:不是内部命令或外部命令。
    • 解决方法:可以在环境变量path中新增一条记录,新添加的路径为xxx的路径
  4. nodemon监控文件内容发生变化
    • js文件可直接通过node执行
    • 通过npm下载(没必要全局下载,全局的话会在环境变量中生成)(npm install nodemon --dev)开发依赖
    • 命令:npx nodemon ./src/index.js(下载了node之后自动生成npm和npx)
    • 可以package.json中scripts中添加以上脚本,直接执行npm run xxx即可(这步可做可不做)

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

1. 函数式编程范式

1.1 为什么学函数式编程?
- 函数式编程是随着react的流行收到越来越多的关注
- vue3也开始拥抱函数式编程
- 函数式编程可以抛弃this
- 有很多库可以帮助我们进行函数式开发:lodash等

1.2 什么是函数式编程?
- 函数式编程(functional programming),FP是编程范式之一,编程范式还有面向过程编程、面向对象编程
- 面向对象编程的思维方式:把现实世界中的事物抽象成程序世界中的类和对象,通过封装、继承和多态来演示事物之间的联系
- 函数式编程:对运算过程进行抽象
    - 函数式编程中的函数指的不是程序中的函数(方法),指的是数学中的函数即映射关系
    - 相同的输入始终会得到相同的输入(纯函数)
    
1.3 函数是一等公民
    - 函数可以存储在变量中
    - 函数可以作为参数
    - 函数可以作为返回值
    
1.4 高阶函数
    - 可以把函数作为参数传递给另一个函数
    - 可以把函数作为另一个函数的返回值
    - 常用高阶函数:forEach/map/filter/every/some/reduce/sort
    // 函数作为另一个函数的返回值(闭包函数)
      function once(fn) {
          let done = false
          return function() {
                  if(!done) {
                          done = true
                          fn.apply(this, arguments)
                  }
          }
      }
      let pay = once(money => {
              console.log(`支付:${money}元`)
      })
      pay(1)
      pay(1)
      pay(1)
      pay(1)
1.5 闭包函数:可以在另一个作用域调用一个函数的内部函数并访问到该函数的作用域中的成员
    - 本质:函数在执行的时候会放到一个执行栈上当函数执行完毕之后会从执行栈上移除,但是堆上的作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数的成员
    - 闭包案例:
    // 第一个是基本工资,第二个是绩效工资
    function makeSalary(x) {
        console.log(this)
        return function(y) {
                console.log(this)
                return x + y
        }
    }

    let makeSalary1 = makeSalary(10000)
    makeSalary1(3000)
    
1.6 纯函数的好处:
    可缓存(因为纯函数对于相同的输入始终会得到相同的结果,所以可以把纯函数的结果缓存起来)
    function memoize(f) {
        let cache = {}
        return function() {
                let key = JSON.stringify(arguments)
                cache[key] = cache[key] || f.apply(this, arguments)
                return cache[key]
        }
    }
    
1.7 副作用:
    - 纯函数:对于相同的输入永远会得到相同的输入,没有任何副作用
    - 所有外部交互都有可能带俩副作用,副作用也使得方法通用性下降不适合扩展和可重用性,同时副作用会给程序中带来安全隐患和不确定性,但是副作用不可能完全制止,尽可能控制他们在可控范围内发生
    // 不纯的
    let mini = 18
    function checkAge(age) {
        return age >= mini
    }
    
    //纯的(有硬编码,后续可以通过柯里化解决)
    function checkAge() {
        let mini = 18
        return age >= mini
    }
    
1.8 柯里化( 解决硬编码的问题)
    - 当一个函数有多个参数的时候先传递一部分参数调用它(这部分参数以后永远不变)
    - 然后返回一个新的函数接收剩余的参数,返回结果
    // 普通纯函数
    function checkAge(min, age) {
        return age > min
    }
    
    // 柯里化
    function checkAge(min) {
        return function(age) {
            return age >= min
        }
    }
    let checkAge18 = checkAge(18)
    checkAge18(20)
    
 1.9 函数组合
     - 纯函数和柯里化很容易写出洋葱代码
     - 如果一个函数要经过多个函数处理才能得到最终值,这个时候可以把中间过程的函数合并成一个函数
         - 函数组合默认是从右到左
     // 多函数组合
     function compose (...fns) {
        return function (value) {
            return fns.reverse().reduce(function (acc, fn) {
                return fn(acc)
            }, value)
        }
     }
  
 1.10 lodash
     - lodash中提供了很多功能,比如柯里化函数等
     const _ = require('lodash')

    // 要柯里化的函数
    function getSum (a, b, c) {
        return a + b + c
    }

    // 柯里化后的函数
    let curried = _.curry(getSum)

    // 测试
    curried(1, 2, 3)
    curried(1)(2)(3)
    curried(1, 2)(3)
    
    -lodash/fp
        - lodash中的fp模块提供了实用的函数式编程友好的方法(都是纯函数)
        - 提供了不可变auto-curried iteratee-first data-last 的方法
        - 常用方法:例如:fp.flowRight() fp.first fp.map fp.split1.11 pointTree(一种模式)
    - 不需要指明处理的数据
    - 只需要合成运算过程
    - 需要定义一些辅助的基本运算函数
    // 非 Point Free 模式

    // Hello World => hello_world
    function 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'))
    
    

2. javascript异步编程

  • Promise的then方法会返回一个全新的Promise对象(所以可以使用链式调用的方式去添加then方法)
  • 后面的then方法是在为上一个then返回的Promise注册回调
  • 前面then方法中回调函数的返回值会作为后面then方法回调的参数
  • 如果回调中返回的是Promise, 那后面then方法的回调会等待他的结果
- 目前大多数异步调用都是作为宏任务执行
- 微任务:Promise & MutationObserver & process.nextTick

- 代码根据执行优先级分为:同步、宏任务、微任务
- 同一层级
    同步(普通代码,promise构造函数中) >> 微任务(nextTick > queueMicrotask, then) >> 宏任务(setTimeout, setInterval)

3. 手写promise源码

finally有两个特点:

1.无论当前的promise对象最终的特点是成功还是失败,finally方法当中的回调始终都会被执行一次
2.在finally方法的后面可以链式调用then方法来拿到当前promise返回的结果

源码:

// 定义三种状态常量 等待、成功、失败
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
	constructor(executor) {
		try {
			executor(this.resolve, this.reject)
		} catch(err) {
			this.reject(err)
		}
	}
	status = PENDING
	value = undefined
	reason = undefined
	successCallback = []
	failCallback = []

	resolve = value => {
		if(this.status !== PENDING) return
		this.status = FULFILLED
		this.value = value
		// this.successCallback && this.successCallback(this.value)
		while(this.successCallback.length) this.successCallback.shift()()
	}
	reject = reason => {
		if(this.status !== PENDING) return
		this.status = REJECTED
		this.reason = reason
		// this.failCallback && this.failCallback(this.reason)
		while(this.failCallback.length) this.failCallback.shift()()
	}

	then(successCallback, failCallback) {
		// 参数可选
		successCallback = successCallback ? successCallback : value => value
		// 失败回调可选
		failCallback = failCallback ? failCallback : reason => { throw reason }
		let promise2 = new MyPromise((resolve, reject) => {
			if(this.status === FULFILLED) {
				setTimeout(() => {
					try {
						let x = successCallback(this.value)
						// 判断 x 的值是普通值还是promise对象
			            // 如果是普通值 直接调用resolve 
			            // 如果是promise对象 查看promsie对象返回的结果 
			            // 再根据promise对象返回的结果 决定调用resolve 还是调用reject
						resolvePromise(promise2, x, resolve, reject)
					} catch(e) {
						reject(e)
					}
					
				}, 0)
			} else if(this.status === REJECTED) {
				setTimeout(() => {
					try{
						let x = failCallback(this.reason)
						resolvePromise(promise2, x, resolve, reject)
					} catch(e) {
						reject(e)
					}
				}, 0)
			} else {

				this.successCallback.push(() => {
					setTimeout(() => {
						try {
							let x = successCallback(this.value)
							resolvePromise(promise2, x, resolve, reject)
						} catch(e) {
							reject(e)
						}
						
					}, 0)
				})
				this.failCallback.push(() => {
					setTimeout(() => {
						try {
							let x = failCallback(this.reason)
							resolvePromise(promise2, x, resolve, reject)
						} catch (e) {
							reject(e)
						}
					}, 0)
				})
			}
		})
		return promise2;
	} 

	static all(array) {
		let result = [];
		let index = 0;
		return new MyPromise((resolve, reject) => {
			function addData(key, value) {
				result[key] = value;
				index++
				if(index === array.length) {
					resolve(result)
				}
			}
			for(let i = 0; i < array.length; i++) {
				let current = array[i];
				if(current instanceof MyPromise) {
					current.then(value => addData(i, value), reason => reject(reason))
				} else {
					addData(i, array[i])
				}
			}
		})
	}

	static resolve(value) {
		if(value instanceof MyPromise) return value
		return new MyPromise(resolve => resolve(value))
	}

	finally(callback) {
		return this.then(value => {
			return MyPromise.resolve(callback()).then(() => value)
		}, reason => {
			return MyPromise.resolve(callback().then(() => {throw reason}))
		})
	}

	catch(failCallback) {
		return this.then(undefined, failCallback)
	}

}

function resolvePromise(promise2, x, resolve, reject) {
	if(promise2 === x) {
		return reject(new TypeError('chain circle error'))
	}
	if(x instanceof MyPromise) {
		x.then(resolve, reject)
	} else {
		resolve(x)
	}
}

module.exports = MyPromise