大前端-阶段1-模块1-JS 异步编程、手写 Promise

一:函数式编程范式

1.学习函数式编程的意义:

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

2.什么是函数式编程(FP):

把现实世界的事物和事物之间的联系抽象到程序世界(对运算过程进行抽 象)。
函数式编程就是对这些过程进行抽象。

3.什么是面向对象编程:

把现实世界中的事物抽象成程序世界中的类和对象,通过封装、继承和多态来演示事物事件的联系。

4.程序的本质:

根据输入通过某种运算获得相应的输出,程序开发过程中会涉及很多有输入和 输出的函数。
函数式编程中的函数指的不是程序中的函数(方法),而是数学中的函数即映射关系,例如:y = sin(x),x和y的关系。
相同的输入始终要得到相同的输出(纯函数) 。
函数式编程用来描述数据(函数)之间的映射。
案例:

// 非函数式
let num1 = 2
let num2 = 3
let sum = num1 + num2 console.log(sum)
// 函数式
function add (n1, n2) {
  return n1 + n2
}
let sum = add(2, 3)
console.log(sum)
5.函数:

函数式一等函数
高阶函数
闭包

  • 函数是一等公民:
    1.函数可以存储在变量中
    2.函数可以作为参数
    3.函数可以作为返回值

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

// 把函数赋值给变量  函数可以存储在变量中
let fn = function () {
console.log('Hello First-class Function') }
fn()
// 一个示例
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) }, destroy (post) { return Db.destroy(post) }
}
// 优化
const BlogController = {
  index: Views.index,
  show: Views.show,
  create: Db.create,
  update: Db.update,
  destroy: Db.destroy
}
6.高阶函数:

1.可以把函数作为参数传递给另一个函数:可以让函数更为灵活,不需要考虑内部是如何实现的。
2.可以把函数作为另一个函数的返回结果。

  1.函数作为参数
// forEach
function forEach(array, fn){
   for (let i=0;i<array.length;i++){
	fn(array[i])
  }
}
let arr =[1,2,3,4,5]
forEach(arr, function(item) {
	console.log(item)
})

// filter
function filter(array, fn) {
  let results = []
  for (let i = 0; i < array.length; i++) {
    if (fn(array[i])) {
      results.push(array[i])
    }
  }
  return results
}
let arr =[1,2,3,5,7]
let r= filter(arr,function (item) {
	return item % 2==0
}) 
console.log(r)

// 2.函数作为返回值
functtion makeFn() {
let msg = 'hello function'
return function () {
	console.log(msg)
  }
}
// 第一种调用方式
const fn = makeFn()
fn()
// 第二种调用方式:
makeFn()()
第一次调用()返回一个函数,第二次的()用来调用返回的函数

函数作为返回值:场景:函数只会执行一次

// 对一个函数只会执行一次
例如支付场景:
function once(fn){
	let done = false // 记录fn函数是否执行了
	return function() {
		if (!done){
			done = true
			// 改变this, 传递参数。
			// arguments是第一个return的函数传递的参数
			return fn.apply(this, arguments)
		}
	}
}
let pay = once(function (money) { 
  console.log(`支付:${money} RMB`)
})
// 只会支付一次 pay(5)
pay(5)
pay(5)
pay(5)

7.使用高阶函数的意义:

1.抽象可以帮助我们屏蔽细节,只需要关注我们的目标
2.高阶函数是用来抽象通用的问题

8.常用高阶函数:

1.forEach
2.map
3.filter
4.every
5.some
6.find/findIndex
7.reduce
8.sort 。。。。
模拟高阶函数:map,every,some


// map:
const map = function (arr, fn) {
	let result = []
	for (let value of arr){
		result.push(fn(value)) // fn处理的结果存储到数组中,
	}
	return result
}


every:
const every = (array, fn) => {
	let result = true // 假设数组中的每一个元素都是匹配fn的条件的
	for(let value of array) {
		result = fn(value)
		// 当有一个条件不满足的时候直接break,返回false
		if (!result) {
			break
		}
	}
	return result
}

some:
const some = (array, fn) => {
	let result = false // 建设所有条件都不满足fn条件
	for (let value of array) {
		result = fn(value)
		// 只要有一个条件满足fn条件,就停止循环
		if (result) {
			break;
		}
	}
	return result
}



9. 闭包概念:

可以在另一个作用域中调用一个函数的内部函数并访问到该函数的作用域中的成员。

如何产生闭包:

  1. 函数作为返回值。
  2. 函数作为参数传递。
10.闭包的本质:

函数在执行的时候会放到一个执行栈上,当函数执行完毕之后会从执行栈上移除,并且内部的变量也会从内存中移除,但是 如果外部对这个函数有引用的话,情况就不一样了,被引用的函数中的变量不能移除。(作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数的成员)

11.闭包的好处:

延长了外部函数内部变量的作用范围。

12.纯函数:

相同的输入永远会得到相同的输出,而且没有任何可观察的副作用。
lodash 是一个纯函数的功能库,提供了对数组、数字、对象、字符串、函数等操作的一些方法。
数组的 slice 和 splice 分别是:纯函数和不纯的函数。
slice 返回数组中的指定部分,不会改变原数组。
splice 对数组进行操作返回该数组,会改变原数组。

使用纯函数的好处:
1.可缓存。 因为纯函数对相同的输入始终有相同的结果,所以可以把纯函数的结果缓存起来。
2.可测试。纯函数让测试更方便。
3.并行处理。
在多线程环境下并行操作共享的内存数据很可能会出现意外情况。
纯函数不需要访问共享的内存数据,所以在并行环境下可以任意运行纯函数 (Web Worker)。

const _ = require('lodash')
function getArea(r) {
  console.log(r)
  return Math.PI * r * r
}
let getAreaWithMemorize = _.memoize(getArea)
console.log(getAreaWithMemorize(4))
console.log(getAreaWithMemorize(4))
console.log(getAreaWithMemorize(4))
 // 4
 // 50.26548245743669
 // 50.26548245743669
 // 50.26548245743669
 说明函数植被执行了一次,后面两次是从缓存中获取到的结果

模拟memoize的实现:

1.接收一个函数,返回一个函数
2.把f的执行结果缓存起来,需要定义一个对象,用来缓存执行的结果。
3. 判断是否已经有结果
function memoize(f) {
  let cache = {}
  return function (){
  // 把arguments伪数组,转为字符串作为对象的键
    let key = JSON.stringify(arguments)
    // 判断是否已经有结果。 如果是第一次执行,则没有值需要f.apply(fn, arguments)执行。 apply不是为了改变this,而是传递参数
    cache[key] = cache[key] || f.apply(f, arguments)
    return cache[key]
  }
}
13.副作用:

纯函数:对于相同的输入永远会得到相同的输出,而且没有任何可观察的副作用。
副作用让一个函数变的不纯(如上例),纯函数的根据相同的输入返回相同的输出,如果函数依赖于外部 的状态就无法保证输出相同,就会带来副作用。
副作用来源:
1.配置文件
2.数据库
3.获取用户的输入
所有的外部交互都有可能产生副作用。副作用使得方法的通用性下降不适合扩展和可用性,同时副作用会给程序中带来安全隐患和不确定性,但是副作用不可能完全静止,尽可能控制他们发生在可控范围内。

14.柯里化:

1.当一个函数有多个参数的时候先传递一部分参数调用它(这部分参数以后永远不变)
2.然后返回一个新的函数接收剩余的参数,返回结果。
把多个参数的函数转化为一个参数的函数。

function checkAge(age) {
	let min = 18
	return age > min
}

// min: 基准值
// 普通的纯函数
function checkAge(min, age) {
	return age > min
}

checkAge(18, 20)
checkAge(18, 20)

checkAge(22, 20)
checkAge(22, 20)


// 函数科里化:调用函数只传入部分的参数,并且让这个函数返回一个新的函数,
// 新的函数接收剩余的参数,并且返回相应的结果
// min:基准值,不容易改变的值
// age:每次调用函数都会发生变化的值
function checkAge(min) {
	return function (age) {
  	return age >=  min
  }
}
// 生成不同基准值的函数
let checkAge18 = checkAge(18)
let checkAge20 = checkAge(20)

// 比较  让用户的年龄和基准值比较
console.log(checkAge18(20))
console.log(checkAge18(24))

// 箭头函数实现函数科里化
let checkAge = min => (age => age >= min)



lodash中的科里化:
_.curry(func)
功能:创建一个函数,该函数接收一个或多个func的参数,如果func所需的参数都被提供则执行funx并返回执行的结果,否则继续返回该函数并等待接收剩余的参数。
参数:需要科里化的函数
返回值:科里化之后的额函数。

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)

总结:
1.柯里化可以让我们给一个函数传递较少的参数得到一个已经记住了某些固定参数的新函数 。
2.这是一种对函数参数的’缓存’。
3.让函数变的更灵活,让函数的粒度更小 可以把
4.多元函数转换成一元函数,可以组合使用函数产生强大的功能

模拟 curry() 的实现:


curried(1,2,3)
curried(1)(2,3)
curried(1,2)(3)

 // func: 需要科里化处理的函数
 // 需要科里化的函数参数个数是不固定的:1.可以一次性全部传递完 2.可以分多次传递参数
function curry(func) {
  return function curriedFn(...args) { // ...args剩余参数
    // 判断实参和形参的个数,看是否相同。如果实际参数的个数<形参的个数,那么此时需要返回一个新的函数,等待传递剩余参数
    // args.length:实际参数的个数
    // func.length:形参的个数
    if (args.length < func.length) {
      return function () {
      // ...args.concat(Array.from(arguments):上一次调用函数的参数和当前函数调用的参数合并
        return curriedFn(...args.concat(Array.from(arguments)))
      }
    }
    // 实参和形参个数相同,调用 func,返回结果
    return func(...args)
  }
}

15.函数组合

纯函数和柯里化很容易写出洋葱代码 h(g(f(x)))。
洋葱代码的执行顺序是(从里到外):先执行里面的函数f(x),执行完之后返回的结果交给g,g执行完之后结果交给h。

函数组合:可以让我们把细粒度的函数重新组合生成一个新的函数。
函数组合 (compose): 如果一个函数要经过多个函数处理才能得到最终值,这个时候可以把中间 过程的函数合并成一个函数。
函数就像是数据的管道,函数组合就是把这些管道连接起来,让数据穿过多个管道形成最终的结果。
函数组合默认是从右到左执行。

总结:
函数组合就是 把多个函数组合成一个新的函数,在执行的过程中我们可以把参数输入给执行的第一个函数,当它执行完成之后,会返回一个中间结果,并且把这个中间结果交给下一个函数去处理,当最后一个函数执行完成之后,我们会把最终的结果返回。

// 函数组合:
// f, g: 函数
function compose(f, g) {
// value: 参数
	return function (value) {
		return f(g(value))
	}
}

解决实际问题:
获取数组最后一个元素
1. 数组反转
2. 获取数组第一个元素

function reverse(arr) {
	return arr.reverse()
}

function first(arr) {
	return arr[0]
}

let arr = [1,2,4,5]
let last = compose(first, reverse)
console.log(last(arr)) // 5


lodash中的函数组合:
1.lodash中组合函数flow()或者flowRight(),他们都可以组合多个函数。
flow():是从左到右运行
flowRight():从右到左运行,使用的多一些。

数组中的最后一个元素大写

const _ = require('lodash')

const reverse = arr => arr.reverse()
const first = arr => arr[0]
const toUpper = str => str.toUpperCase()

函数组合:
const fn = _.flowRight(toUpper, first, reverse)
console.log(fn(['one', 'two', 'three']))

模拟实现 lodash 的 flowRight 方法:


// 多函数组合
// fns : 传入的多个函数
function compose(...fns) {
// value: 参数
  return function (value) {
    return fns.reverse().reduce(function (acc, fn) { // 此处的fn就是数组中的每一个函数,通过这个函数来处理传递的参数,当处理完成返回
      return fn(acc)
    }, value) // acc初始的值是我们第一次调用function的时候传递的value。
  }
}
// ES6
const compose = (...fns) => value => fns.reverse().reduce((acc, fn) => fn(acc), value)


16.Point Free

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

  • 不需要知名处理的数据
  • 只需要合成运算的过程
  • 需要定义一些辅助的基本运算函数

Point Free实际就是函数的组合

const f= fp.flowRight(fp.join('-'),fp.map(_.toLower), fp.split(' '))

案例演示:

// 把空格转换成_
const fp = require('lodash/fp')
const f = fp.flowRight(fp.replace(/\s+/g, '_'),fp.toLower)
console.log(f('hello world')) // hello_world
// 把子母中的第一个字母提取出来并转换成大写,使用,作为分隔符
// world wild web ==>W.W.W
const fp = require('lodash/fp')
// const firstLetterToUpper = fp.flowRight(fp.join('.'),fp.map(fp.first),fp.map(fp.toUpper),fp.split(' '))
const firstLetterToUpper = fp.flowRight(fp.join('.'),fp.map(fp.flowRight(fp.first,fp.toUpper)),fp.split(' '))
console.log(firstLetterToUpper("world wild web")) // W.W.W
17. Functor (函子)

到目前为止已经已经学习了函数式编程的一些基础,但是我们还没有演示在函数式编程中如何把副作用控制在可控的范围内、异常处理、异步操作等。
什么是Functor:

  • 容器:包含值和值的变形关系(这个变形关系就是函数)
  • 函子:是一个特殊的容器,通过一个普通的对象来实现,该对象具有map方法,map方法可以运行一个函数对值进行处理(变形关系)
// Functor (函子)
class Container{
  constructor(value) {
    // 不想被外部访问
    this._value = value
  }
  map(fn) {
    return new Container(fn(this._value))
  }
}

// 创建函子new Container(5)
// 调用函子中的map方法
let r = new Container(5).map(x => x + 1).map( x => x * x)
console.log(r) // Container { _value: 36 }
console.log(r._value) // 36

不想用new方法调用

class Container{
  // of方法的作用:返回一个函数对象
  static of (value) {
    return new Container(value)
  }
  constructor(value) {
    this._value = value
  }
  map(fn) {
    return Container.of(fn(this._value))
  }
}

let r =Container.of(5).map(x => x + 1 ).map(x => x * x)
console.log(r) // Container { _value: 36 }

总结:

  • 函数式编程的运算不直接操作值,而是由函子来完成
  • 函子就是一个实现了map契约的对象
  • 我们可以把函数想像成一个盒子,这个盒子里封装了一个值
  • 想要处理盒子中的值,我们需要给盒子的map方法传递一个处理值的方法,由这个函数来对值进行处理
  • 最终map方法返回一个包含新值的盒子(函子)
MayBe(函子):处理空值的问题。(传入null / undefined)
// 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))
  }

  // 处理异常
  isNothing() {
    return this._value === null || this._value === undefined
  }
}
let r = MayBe.of('hello world').map(x=> x.toUpperCase())
console.log(r) // MayBe { _value: 'HELLO WORLD' }
let r1 = MayBe.of(null).map(x=> x.toUpperCase())
console.log(r1) // MayBe { _value: null }
let r2 = MayBe.of(undefined).map(x=> x.toUpperCase()).map(x => null).map(x=>x.split(' '))
console.log(r2) // MayBe { _value: null }

虽然可以异常,但是不能捕获在哪一步出现了空值

Either函子
  • Either类似于if。。。else的处理。Either两者中的任何一个
  • 异常回放函数变的不纯,Either函子可以用来处理异常
// Either函子
// Left处理异常
class Left {
  static of (value) {
    return new Left(value)
  }
  constructor(value) {
    this._value = value
  }

  map(fn){
    return this
  }
}

// Right处理正常的值
class Right {
  static of (value) {
    return new Right(value)
  }
  constructor(value) {
    this._value = value
  }

  map(fn){
    return Right.of(fn(this._value))
  }
}

// 测试代码
let r1= Right.of(12).map(x => x + 2)
let r2 = Left.of(12).map(X => X + 2)
console.log(r1) // Right { _value: 14 }
console.log(r2) // Left { _value: 12 }

// 测试
function parseJSON(str) {
  try {
    return Right.of(JSON.parse(str))
  } catch (e) {
    return Left.of({error: e.message})
  }
}
let r3 = parseJSON('{name: zce}')
console.log(r3)  // Left { _value: { error: 'Unexpected token n in JSON at position 1' } }
let r4 = parseJSON('{"name": "zce"}').map( x => x.name.toUpperCase())
console.log(r4) // Right { _value: 'ZCE' }
IO函子
  • IO函子中的_value是一个函数,这里是把函数作为值来处理
  • IO函子可以把不纯的动作存储到_value中,延迟执行这个不纯的操作,包装到当前的操作
  • 把不纯的操作交给调用者来处理
// Io函子
const fp = require('lodash/fp')
class Io{
  static of (value) {
    return new Io(function () {
      return value
    })
  }

  constructor(fn) {
    this._value = fn
  }

  map(fn) {
    return new Io(fp.flowRight(fn, this._value))
  }
}

let r = Io.of(process).map(p => p.execPath)
console.log(r) // Io { _value: [Function (anonymous)] }
console.log(r._value()) // /usr/local/bin/node
Task异步执行
  • 异步任务的实现过于复杂,我们使用folktale中的Task来演示
  • folktale一个标准的函数式编程库
  • 和lodash,ramda不同的是,它没有提供很多功能函数
  • 只提供了一些函数式处理的操作,例如:coompose,curry等,一些函子Task,Either,Maybe等
// folktale中的compose, curry
const { compose, curry } = require('folktale/core/lambda')
let f = curry(2, (x, y) => {
  return x + y
})
console.log(f(1, 2)) // 3
console.log(f(1)(2)) // 3

const  {toUpper, first } =require('lodash/fp')
let f1 = compose( toUpper,first )
console.log(f1(['one', 'two'])) // ONE
Pointed函子
  • Pointed函子式实现了of静态方法的函子
  • of方法是为了避免使用new来创建对象,更深层的含义是of方法用来把值放到上下文context(把值方法哦容器中,使用map方法来处理值)
IO函子问题

函子嵌套

// IO 函子的问题
const fs = require('fs')
const fp = require('lodash/fp')

class IO {
  static of (value) {
    return new IO(function () {
      return value
    })
  }

  constructor (fn) {
    this._value = fn
  }

  map (fn) {
    return new IO(fp.flowRight(fn, this._value))
  }
}

let readFile = function (filename) {
  return new IO(function () {
    return fs.readFileSync(filename, 'utf-8')
  })
}

let print = function (x) {
  return new IO(function () {
    console.log(x)
    return x
  })
}

let cat = fp.flowRight(print, readFile)
// IO(IO(x))
let r = cat('package.json')
console.log(r) // IO { _value: [Function (anonymous)] }
console.log(r._value()._value()) // {"name": "text",....}
Monad(单子)
  • Monad函子是可以变扁的pointed函子。Io(Io(x))
  • 一个函子如果具有join和of两个方法并遵守一些定律就是一个Monad
// IO 函子的问题
const fs = require('fs')
const fp = require('lodash/fp')

class IO {
  static of (value) {
    return new IO(function () {
      return value
    })
  }

  constructor (fn) {
    this._value = fn
  }

  map (fn) {
    return new IO(fp.flowRight(fn, this._value))
  }

  // 为了解决函子嵌套
  join() {
    return this._value()
  }
  flatMap(fn) {
    return this.map(fn).join()
  }

  
}

let readFile = function (filename) {
  return new IO(function () {
    return fs.readFileSync(filename, 'utf-8')
  })
}

let print = function (x) {
  return new IO(function () {
    console.log(x)
    return x
  })
}

let r = readFile('package.json')
        .map(fp.toUpper)
        .flatMap(print).join()
console.log(r)
// {
//   "NAME": "TEXT",
//   "VERSION": "1.0.0",
//   "DESCRIPTION": "",
//   "MAIN": "INDEX.JS",
//   "SCRIPTS": {
//     "TEST": "ECHO \"ERROR: NO TEST SPECIFIED\" && EXIT 1"
//   },
//   "KEYWORDS": [],
//   "AUTHOR": "",
//   "LICENSE": "ISC",
//   "DEVDEPENDENCIES": {
//     "TYPESCRIPT": "^4.3.5"
//   },
//   "DEPENDENCIES": {
//     "FOLKTALE": "^2.3.2",
//     "LODASH": "^4.17.21"
//   }
// }

JavaScript 异步编程

javascript为什么是单线程?

参考: https://blog.csdn.net/weixin_48786946/article/details/107523484

与javascript的设计初衷有关,最早javascript是运行在浏览器中的脚本语言,目的是为了实现页面上的动态交互,实现页面的核心是dom操作,所以决定了javascript是单线程,否则会出现线程同步的问题:

比如多个线程同时操作一个dom元素,这时浏览器不知道要以谁的操作为准,造成代码执行顺序混乱。
javascript是单线程也就意味着浏览器有多个任务的时候要排队依次进行,一个一个完成,这种模式的优点是比较安全。缺点是如果我们遇到一个特别消耗时间的任务,那么后面的任务就会一直等着这个任务的完成,这样会造成页面卡死的情况,会造成阻塞。

函数的声明不会产生任何的调用,只有调用的时候才会执行。

同步编程:

指的是我们的javascript代码要依次执行,后面的代码要等待前一句代码执行完成才能执行,排队执行,javascript代码大多数都是以同步模式进行执行的。
押入js执行栈–>执行代码–>执行完毕–>弹出调用栈

函数的声明,不会产生任何的调用,直到调用这个函数

会产生阻塞,页面会卡死,产生了异步

异步编程

指的是我们的javascript代码不会等待前面的代码执行完毕才开始执行。
对于耗时的任务都是:开启过后就立即往后执行下一个任务。
耗时任务的后续逻辑一般会通过回调函数的方式定义。
耗时任务执行完成之后会自动执行回调函数。

重要性:单线程的js语言无法同时处理大量耗时任务。

异步难点:代码的执行顺序混乱。

分析异步执行的过程

console.log('global begin')
setTimeout(function timer1() {
	console.log('timer1 invoke')
}, 1800)

setTimeout(function timer2() {
	console.log('timer2 invoke')
	setTimeout(function inner() {
		console.log('inner')
	}1000)
}, 1000)
console.log('global end')

执行顺序为:
global begin
global end
timer2 invoke
timer1 invoke
inner

大前端-阶段1-模块1-JS 异步编程、手写 Promise_第1张图片
大前端-阶段1-模块1-JS 异步编程、手写 Promise_第2张图片

web api: 内部api的环境
call stack 执行环境栈。正在执行的环境表。
queue:消息队列(回调队列)
event loop:负责监听调用栈和消息队列,一旦调用栈所有的任务都结束了,就去消息队列中取出第一个回调函数,压入到调用栈。 (待办事项的环境表。)

倒计时器开启之后,就会开始倒计时,根本不会管调用栈/队列当中是什么情况。

我们将执行的代码放入到调用栈中执行,如果是同步的直接执行,如果是异步的则放入消息队列中等待执行,等到所有的代码执行完毕,我们的event loop就上场了,它会监听调用栈和消息队列中的任务,当调用栈中所有的任务结束以后,它会从消息队列中依次取出回调函数压入到调用栈,开始执行,直到整个循环结束。

eventloop:

主线程从消息队列中读取事件,这个过程是循环不断的,所以整个的这种运行机制称为Event Loop(事件循环),Event Loop是javascript的执行机制。

消息队列:

消息队列是暂时存放异步任务的地方,我们的异步代码会存放消息队列中,等同步代码执行完毕以后,eventloop从消息队列中依次取出异步任务放到调用栈中再次执行。

宏任务,微任务

宏任务:当前调用栈中执行的代码成为宏任务,包括 主代码快 ,定时器。
微任务:宏任务执行完,在下一个宏任务开始之前需要执行的任务,可以理解为回调函数。
运行机制:

1.在执行栈中执行一个宏任务
2.执行过程中遇到微任务,将微任务添加到消息队列中
3.当前宏任务执行完毕,立即执行微任务队列中的任务
4.微任务执行完毕后,把下一个宏任务放到消息队列中,通过eventloop放到调用栈中执行。

回调函数

JavaScript 语言对异步编程的实现,就是回调函数。所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。回调函数的英语名字callback,直译过来就是"重新调用"。

异步编程的根本是:回调函数。

回调函数本身并没有问题,它的问题出现在多个回调函数嵌套。

什么是回调函数?回调函数是调用者定义,交给执行者执行的函数。 简单的说:就是把函数当作参数传递。
其实就是调用者告诉执行者异步任务结束后应该做什么。

function foo(callback) {
	setTimeout(function () {
		callback()
	}, 2000)
}

foo (function() {
	console.log('这就是一个回调函数')
})
promise(es2015):

Promise 的写法只是回调函数的改进,使用then方法以后,异步任务的两段执行看得更清楚了,除此以外,并无新意。

Promise 的最大问题是代码冗余,原来的任务被 Promise 包装了一下,不管什么操作,一眼看去都是一堆then,原来的语义变得很不清楚。

resolve:的作用就是将promise的状态改为:fullied。成功。
reject::的作用就是将promise的状态改为 rejected:失败。参数是reason:失败的理由。

const promise = new Promise((resolev, reject) => {
	resolev('d')
})
promise.then((data) => {}, (error) => {})

基本应用:使用promise封装ajax

function ajax(url) {
	return new Promise((resolve, reject) => {
		var xhr = new XMLHttpRequest() // 创建xhr对象
		xhr.open('GET', url) // 设置xhr的请求方式,请求地址
		xhr.responseType = 'json' // 设置相应类型为json,在请求完成之后直接拿到一个json对象,而不是一个字符串
		xhr.onload = function () { // 注册xhr的onload事件,请求完成4的状态才会执行
			if (this.status === 200) {
				resolve(this.response) // 请求得到的相应结果this.response
			} else {
				reject(new Error(this.statusText))
			}
		}
		xhr.send() // 开始执行异步请求
	})
}

测试:
ajax().then((res) => {consoe.log(res)}, (error) => {console.log(error)})

链式调用then
1.promise对象的then方法返回一个全新的promise对象。
2.后面的then方法就是在为上一个then返回的promise注册回调。
3.前面then方法中回调函数的返回值会作为后面then方法回调的参数。
4.如果回调中返回的是promise对象,那后面的then方法的回调会等待它的结束。

异常处理:.catch, then的第2个函数,try catch

静态方法: Promise.resolve(), Promise.reject()

串联(then):一个任务执行完成,才会执行下一个任务
并行执行(all,race):promise.all(),等待所有的任务结束,才会结束。都成功结束,返回的结果是成功,任何一个失败,都以失败结束。 promise.race():只会等待第一个结束的任务。

并行:promise.all(),promise.race()
promise.all():接收的是一个数组,数组中的每一项是一个promise对象。数组中的每一项都是一个任务。返回值:是一个全新的promise对象,是一个数组,数组中包含每一项异步任务的结果。等待所有的任务结束,才会结束。

promise.race(): 只会等待第一个结束的任务。只要有任何一个任务完成,任务就会结束。

案例:串联 (.then)+ 并行(.all)


// ajax('/api/api.json'):获取请求接口url
ajax('/api/api.json').then((data) => {
	const urls = data.values()
	const tasks = urls.map(url => ajax(url))
	return promise.all(tasks) 
})
.then((values) => {
	console.log(values)	
})

执行时序(执行顺序):

必须等待所有的同步代码执行完后,才会执行回调中的任务。

console.log('strat')
Promise.resolve()
.then((() => {
	console.log('promise')
}))
.then((() => {
	console.log('promise 2')
}))
.then((() => {
	console.log('promise 3')
}))
console.log('end')

// 输出
start
end
promise
promise2 
promise3

promise执行时序:

SetTimeout回调队列中的任务称为宏任务。宏任务执行过程中可能会加一些额外的需求,那么对于这些额外的需求可以选择作为一个新的宏任务进到队列中排队,也可以作为当前任务的微任务,直接在当前任务结束后立即执行。

promise的回调(then, catch)会作为微任务执行。
微任务:提高整体的相应能力。

像通过Promise、MutationObserver、process.nextTick产生的任务都为微任务。
SetTimeout: 宏任务

Generator 生成器函数:

生成器函数比普通的函数多了一个*号,调用生成器函数并不会立即执行这个函数,而是得到一个生成器对象。直到手动调用这个next方法,这个函数体才会开始执行。函数内部可以使用yield返回一个关键值。返回的值中done属性表示是否已经全部执行完了。yield关键字并不会像return关键字一样结束执行,它只是暂停这个生成器函数的执行。直到下次调用next方法,才会从yield暂停的位置开始执行。yield如果后面跟了参数表示返回值。如果手动调用throw方法,捕获生成器内部的异常。
.throw表示抛出异常,函数继续执行的时候,就可以得到这个异常。
*
yeild
next

function *main() {
	try{
		const users = yield ajax('/api/users.json')
		console.log(users)
	} catch(err){
		console.log(err)
	}

}
// g: 是Generator函数返回的生成器函数。
const g = main()
处理失败的生成器函数
g.throw(error)
第一次调用next方法,这个函数的函数体才会开始执行。一直执行到yield后面的一句。执行完成
之后就会暂停,等到下次再调用next方法,继续执行。yield后面的值作为下次next方法的value。
const result = g.next()
result.value.then((data) => {
	const result2 = g.next(data)
})


co:执行器。

function co(generator) {
	const g = generator()
	function handleResult(result) {
		result.value.then(data => {
			handleResult(g.next(data))
		}, error => {
			g.throw(error)
		})
	}
	handleResult(g.next())
}
co(main)
Async / Await 语法糖

Async / Await是generator的语法糖。

  • *改成async
  • yield改成await。 await只能在函数内部使用,不能在外部使用。
  • async / await不需要co这样的执行器
  • async返回promise对象

手写 Promise 源码

手写promise源码步骤:https://blog.csdn.net/weixin_38245947/article/details/122199980

const PENDING = 'pending'; // 等待
const FULFILLED = 'fulfilled'; // 成功
const REJECTED = 'rejected'; // 失败

class MyPromise {
  constructor (executor) {
    try {
      executor(this.resolve, this.reject)
    } catch (e) {
      this.reject(e);
    }
  }
  // promsie 状态 
  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 promsie2 = new MyPromise((resolve, reject) => {
      // 判断状态
      if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            let x = successCallback(this.value);
            // 判断 x 的值是普通值还是promise对象
            // 如果是普通值 直接调用resolve 
            // 如果是promise对象 查看promsie对象返回的结果 
            // 再根据promise对象返回的结果 决定调用resolve 还是调用reject
            resolvePromise(promsie2, x, resolve, reject)
          }catch (e) {
            reject(e);
          }
        }, 0)
      }else if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let x = failCallback(this.reason);
            // 判断 x 的值是普通值还是promise对象
            // 如果是普通值 直接调用resolve 
            // 如果是promise对象 查看promsie对象返回的结果 
            // 再根据promise对象返回的结果 决定调用resolve 还是调用reject
            resolvePromise(promsie2, x, resolve, reject)
          }catch (e) {
            reject(e);
          }
        }, 0)
      } else {
        // 等待
        // 将成功回调和失败回调存储起来
        this.successCallback.push(() => {
          setTimeout(() => {
            try {
              let x = successCallback(this.value);
              // 判断 x 的值是普通值还是promise对象
              // 如果是普通值 直接调用resolve 
              // 如果是promise对象 查看promsie对象返回的结果 
              // 再根据promise对象返回的结果 决定调用resolve 还是调用reject
              resolvePromise(promsie2, x, resolve, reject)
            }catch (e) {
              reject(e);
            }
          }, 0)
        });
        this.failCallback.push(() => {
          setTimeout(() => {
            try {
              let x = failCallback(this.reason);
              // 判断 x 的值是普通值还是promise对象
              // 如果是普通值 直接调用resolve 
              // 如果是promise对象 查看promsie对象返回的结果 
              // 再根据promise对象返回的结果 决定调用resolve 还是调用reject
              resolvePromise(promsie2, x, resolve, reject)
            }catch (e) {
              reject(e);
            }
          }, 0)
        });
      }
    });
    return promsie2;
  }
  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)
  }
  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) {
          // promise 对象
          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));
  }
}

function resolvePromise (promsie2, x, resolve, reject) {
  if (promsie2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise #'))
  }
  if (x instanceof MyPromise) {
    // promise 对象
    // x.then(value => resolve(value), reason => reject(reason));
    x.then(resolve, reject);
  } else {
    // 普通值
    resolve(x);
  }
}

你可能感兴趣的:(大前端学习,javascript)