3-函数式编程、JS 异步编程、手写 Promise(01-01-03)

132 阅读9分钟

1、浏览器是多线程,但是js是单线程,原因

js目的是为了实现页面上的动态交互。实现页面交互的核心就是DOM操作,这就决定了它必须使用单线程模型,否则就会出现很复杂的线程同步问题。

2、单线程的优势和弊端

这种模式最大的优势就是更安全,更简单,缺点也很明确,就是如果中间有一个特别耗时的任务,其他的任务就要等待很长的时间,出现假死的情况。

3、同步模式

同步模式 :指的是代码的任务依次执行,后一个任务必须等待前一个任务结束才能开始执行。程序的执行顺序和代码的编写顺序是完全一致的。在单线程模式下,大多数任务都会以同步模式执行。

4、异步模式

异步模式 不会去等待这个任务的结束才开始下一个任务,都是开启过后就立即往后执行下一个任务。耗时函数的后续逻辑会通过回调函数的方式定义。在内部,耗时任务完成过后就会自动执行传入的回调函数。

5、事件循环机制

主线程运行的时候,会产生堆(heap)和栈(stack),其中堆为内存、栈为函数调用栈。我们能看到,Event Loop 负责执行代码、收集和处理事件以及执行队列中的子任务,具体包括以下过程。

  1. JavaScript 有一个主线程和调用栈,所有的任务最终都会被放到调用栈等待主线程执行。
  2. 同步任务会被放在调用栈中,按照顺序等待主线程依次执行。
  3. 主线程之外存在一个回调队列,回调队列中的异步任务最终会在主线程中以调用栈的方式运行。 4 .同步任务都在主线程上执行,栈中代码在执行的时候会调用浏览器的 API,此时会产生一些异步任务。
  4. 异步任务会在有了结果(比如被监听的事件发生时)后,将异步任务以及关联的回调函数放入回调队列中。
  5. 调用栈中任务执行完毕后,此时主线程处于空闲状态,会从回调队列中获取任务进行处理。 上述过程会不断重复,这就是 JavaScript 的运行机制,称为事件循环机制(Event Loop)。

6、即便promise中没有任何的异步操作,then方法的回调函数仍然会进入到事件队列中排队。

7、promise基本应用

const promise = new Promise((resolve, reject) => {
  resolve(100) // 承诺达成
  reject()// 承诺失败
})

promise.then((value) => {
  console.log('resolved', value) // resolve 100
},(error) => {
  console.log('rejected', error)
})

8、Promise的本质

本质上也是使用回调函数的方式去定义异步任务结束后所需要执行的任务。这里的回调函数是通过then方法传递过去的

9、Promise链式调用

常见误区 嵌套使用的方式是使用Promise最常见的误区。要使用promise的链式调用的方法尽可能保证异步任务的扁平化。 链式调用的理解 promise对象then方法,返回了全新的promise对象。可以再继续调用then方法,如果return的不是promise对象,而是一个值,那么这个值会作为resolve的值传递,如果没有值,默认是undefined 后面的then方法就是在为上一个then返回的Promise注册回调 前面then方法中回调函数的返回值会作为后面then方法回调的参数 如果回调中返回的是Promise,那后面then方法的回调会等待它的结束

10、Promise.all 多个promise合并成一个 等待最后一个任务完成后 结束

var promise = Promise.all([
  ajax('/api/users.json'),// 一个promise 对象
  ajax('/api/posts.json')
])

promise.then(function (values) {
  console.log(values)  // 2个数组,执行结果
}).catch(function (error) {
  console.log(error)   // 失败执行
})

ajax('/api/urls.json') // 串行
  .then(value => {
    const urls = Object.values(value)
    const tasks = urls.map(url => ajax(url))
    return Promise.all(tasks)  // 并行
  })
  .then(values => {
    console.log(values)
  })

11、Promise.race 实现超时控制 只会等待第一个结束的任务

// Promise.race 实现超时控制

const request = ajax('/api/posts.json')
const timeout = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error('timeout')), 500)
})
request先回来,就成功调用then,request 超过500ms的话,直接报错超时
Promise.race([
  request,
  timeout
])
.then(value => {
  console.log(value)  
})
.catch(error => {
  console.log(error)
})

12、课堂练习

async function async1(){
  console.log(1)
  const result = await  async2()
  console.log(3)
}
async function async2(){
  console.log(2)

}

Promise.resolve().then(()=>{
  console.log(4)
})
setTimeout(()=>{
  console.log(5)
})
async1()
console.log(6)
--------
1 2 6 4 3 5 

13、事件循环机制(Event Loop)

主线程运行的时候,会产生堆(heap)和栈(stack),其中堆为内存、栈为函数调用栈。我们能看到,Event Loop 负责执行代码、收集和处理事件以及执行队列中的子任务,具体包括以下过程。

  1. JavaScript 有一个主线程和调用栈,所有的任务最终都会被放到调用栈等待主线程执行。
  2. 同步任务会被放在调用栈中,按照顺序等待主线程依次执行。
  3. 主线程之外存在一个回调队列,回调队列中的异步任务最终会在主线程中以调用栈的方式运行。
  4. 同步任务都在主线程上执行,栈中代码在执行的时候会调用浏览器的 API,此时会产生一些异步任务。
  5. 异步任务会在有了结果(比如被监听的事件发生时)后,将异步任务以及关联的回调函数放入回调队列中。
  6. 调用栈中任务执行完毕后,此时主线程处于空闲状态,会从回调队列中获取任务进行处理。 上述过程会不断重复,这就是 JavaScript 的运行机制,称为事件循环机制(Event Loop)。

事件循环中的异步回调队列有两种:宏任务(MacroTask)和微任务(MicroTask)队列。

宏任务:包括 script 全部代码、setTimeout、setInterval、setImmediate(Node.js)、requestAnimationFrame(浏览器)、I/O 操作、UI 渲染(浏览器),这些代码执行便是宏任务。

微任务:包括process.nextTick(Node.js)、Promise、MutationObserver,这些代码执行便是微任务。

在浏览器的异步回调队列中,宏任务和微任务的执行过程如下:

  1. 宏任务队列一次只从队列中取一个任务执行,执行完后就去执行微任务队列中的任务。
  2. 微任务队列中所有的任务都会被依次取出来执行,直到微任务队列为空。
  3. 在执行完所有的微任务之后,执行下一个宏任务之前,浏览器会执行 UI 渲染操作、更新界面。

14、generator 生成器函数 yield是什么

  1. yield是ES6的新关键字,使生成器函数执行暂停,yield关键字后面的表达式的值返回给生成器的调用者。它可以被认为是一个基于生成器的版本的return关键字。 yield关键字实际返回一个IteratorResult(迭代器)对象,它有两个属性,value和done,分别代表返回值和是否完成。
  2. yield无法单独工作,需要配合generator(生成器)的其他函数,如next,懒汉式操作,展现强大的主动控制特性。
  3. 当yield在赋值表达式的右边,比如 var result = yield 4 ,记住这句话,yield语句本身没有返回值,或者说返回值是undefined,但是当我们调用next(param)传参的时候,param不但作为next返回对象的value值,它还作为上一条yield 的返回值,所以result 才会被成功赋值。

15、

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

result.value.then(data => {
  g.next(data)
})

16、promise 基础数据

  1. 一个promise可能有三种状态:等待(pending)、已完成(fulfilled)、已拒绝(rejected)

  2. 一个promise的状态只可能从“等待”转到“完成”态或者“拒绝”态,不能逆向转换,同时“完成”态和“拒绝”态不能相互转换

  3. promise必须实现then方法(可以说,then就是promise的核心),而且then必须返回一个promise,同一个promise的then可以调用多次,并且回

  4. 调的执行顺序跟它们被定义时的顺序一致 then方法接受两个参数,第一个参数是成功时的回调,在promise由“等待”态转换到“完成”态时调用,另一个是失败时的回调,在promise由“等待”态转换到“拒绝”态时调用。同时,then可以接受另一个promise传入,也接受一个“类then”的对象或方法,即thenable对象。

  5. promise就是一个类 在执行类的时候需要传递一个执行器进去,执行器会立即执行

  6. Promise中有三种状态,分别为成功-fulfilled 失败-rejected 等待-pending pending -> fulfilled pending -> rejected

  7. 一旦状态确定就不可更改 resolve 和 reject函数是用来更改状态的 resolve:fulfilled reject:rejected

  8. then方法内部做的事情就是判断状态 如果状态是成功,调用成功回调函数 如果状态是失败,就调用失败回调函数 then方法是被定义在原型对象中的

  9. then成功回调有一个参数,表示成功之后的值;then失败回调有一个参数,表示失败后的原因

promise.all

// 如果p1是两秒之后执行的,p2是立即执行的,那么根据正常的是p2在p1的前面。
// 如果我们在all中指定了执行顺序,那么会根据我们传递的顺序进行执行。
function p1 () {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('p1')
    }, 2000)
  })
}

function p2 () {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('p2')
    },0)
  })
}

Promise.all(['a', 'b', p1(), p2(), 'c']).then(result => {
  console.log(result)
  // ["a", "b", "p1", "p2", "c"]
})

分析一下:

  1. all方法接收一个数组,数组中可以是普通值也可以是promise对象
  2. 数组中值得顺序一定是我们得到的结果的顺序
  3. promise返回值也是一个promise对象,可以调用then方法
  4. 如果数组中所有值是成功的,那么then里面就是成功回调,如果有一个值是失败的,那么then里面就是失败的 使用all方法是用类直接调用,那么all一定是一个静态方法