JavaScript——Promise和异步编程

前言

JavaScript天生就是支持异步编程的,但是最开始的异步编程方法是事件模型回调函数,这两种方式在面对复杂情况时显得力不从心,而Promise就是异步编程的一种解决方法。

异步编程的背景知识

由于Js引擎是基于单线程事件循环的概念构建的,所以同一时刻,只允许一个代码块执行。

任务队列(job queue):每当一段代码将要运行时就会被添加到任务队列中。

事件循环(event loop):当一段代码执行完,会执行任务队列中的下一个任务。

事件模型

let button = documnet.getElementById('btn')
button.onclick = function(event){
    console.log('clicked')
}
复制代码

这是JavaScript中最基础的异步编程形式,直到事件触发时才执行事件处理程序。 事件模型适用于响应用户交互和完成类似的低频功能。

回调模式

回调模式与事件模型类似,异步代码都会在未来的某个时间点执行。

readFile("example.txt", function(err, contents) {
      if(err) {
          throw err;
      }
      console.log(contents)
})
console.log('Hi');
复制代码
  • 分析这段代码:由于使用了回调模式,readFile 函数会立即执行,当读取磁盘上文件时,暂停执行,交出 Js 引擎线程,console.log('Hi') 会立即执行。当readFile结束执行时,会向任务队列的末尾添加一个新任务,该任务包含回调函数和相应的参数(就是闭包咯),待队列前执行完,执行该任务。

回调模式如果嵌套太多的回调函数,会导致代码难以理解,使自己陷入回调地狱。

Promise的基础知识

Promise相当于是异步操作的占位符,会让函数返回一个表示异步操作的Promise对象,未来对这个对象的操作取决于Promise的生命周期。

Promise的生命周期

  • 进行中(pending
  • 已处理(settled)
    • 成功(fulfilled
    • 未成功(rejected

所有的 promise 对象都要实现then()方法,接受两个参数:第一个是当promise的状态变为 fulfilled 时要调用的函数,与异步操作相关的附加数据都会传递给这个完成函数;第二个是 promise 状态变为 rejected 时要调用的函数。

Promise 对象还有一个 catch()方法,相当于只传第二个参数的 then

一个对象如果实现了then方法,就称为thenable对象,所有的 promise 对象都是 thenable 对象,但 thenable 对象不一定是 promise 对象。

创建未完成的 Promise

Promise 的构造函数可以创建新的 Promise 。构造函数只接受一个参数:包含初始化Promise代码的执行器(executor)函数。执行器函数有两个参数,分别是·esolve函数和reject函数,执行器成功完成时调用 resolve 函数,失败时调用 reject 函数。

let promise = new Promise(resolve, reject) {
    console.log('promise执行器');
    resolve();
}
promise.then(function() {
    console.log('Resolved')
})
console.log('hi')

// 执行结果
// promise执行器
// hi
// Resolved
复制代码
  • 调用 resolve 后会触发一个异步操作,将传入thencatch中的方法加入任务队列中,并异步执行。

任务编排(job scheduling):当编排任务时,会向任务队列中添加一个新任务,并明确指定将任务延后执行。类似setTimeout。

resolve()reject() :定义在 then 或 catch 中,执行在 promise 构造函数。

创建已处理的 Promise

  • Promise.resolve()Promise.reject() 创建完成态 / 已拒绝的 Promise。也就是说不会有任务编排的过程,而且需要需要向 Pormise 添加一至多个完成处理程序(then/catch 的参数)来获取值。
let promsie = Pormise.resolve(42);
promise.then(function(value) {
    console.log(value) // 42
}
复制代码

全局的 Promise 拒绝处理

Pormise 的一个问题是,任何时候都可以调用 then 方法和 catch 方法,无论 Promise 是否已解决这两个方法都可以正常运行。

  1. 在Node.js的环境中

在Node.js中处理promise拒绝会触发process对象上的两个事件:

  • unhandledRejection :在一个事件循环中,当Promise被拒绝,且没有提供拒绝处理程序时,触发该事件。
  • rejectionHandled:在一个事件循环后,当Promise被拒绝时,若拒绝处理程序被调用,触发该事件。
  1. 在浏览器环境

浏览器也会触发上述两个事件,且这些事件绑定在 window 对象上。

串联 Promise

Pormise 每次调用 then() 方法或 catch() 方法时,实际上创建并返回了另一个 Promise。也就是promise的then可以链式调用。而且只有当前一个执行完了才可以执行下一个。

let promise = new Promise(function(resolve,reject) {
    resolve(42)
})
promise.then(function(value) {
    console.log(42)
    throw new Error('Boom!')
}).catch(function(err){
    console.log(err.message)
})
复制代码
  • 在 promise 链中,返回值可以用在下一个 Pormise 的完成处理函数中。
  • 在 Promise 链中,返回 Promise 对象。(原本的then就是返回一个promise对象,这里就是显式指定了一下)
let p1 = new Promise(function(resolve, reject) {
    resolve(42)
})
let p2 = new Promise(function(resolve, reject) {
    resolve(43)
})
p1.then(function(value) {
    console.log(value)  // 42
    return p2
}).then(function(value) {
    console.log(value)  // 43
})
复制代码

响应多个 Promise

  • Promise.all():接受一个参数并返回一个 Promise,参数是一个含有多个受监视 Promise 的可迭代对象(例如一个数组),只有当可迭代对象中所有 Promise 都被解决后返回的 Promise 才会被解决。传入 Promise.all() Promise 中,只要有一个被拒绝,那么返回的 Promise 不等所有 Promise 都完成就立即被拒绝。
let p1 = new Promise(function(resolve, reject) {
    resolve(42)
})
let p2 = new Promise(function(resolve, reject) {
    resolve(43)
})
let p3 = new Promise(function(resolve, reject) {
    resolve(44)
})
let p4 = Promise.all([p1, p2, p3])
p4.then(function(value) {
    console.log(value.isArray()) // true
    console.log(value[0]); // 42
    console.log(value[1]); // 43
    console.log(value[2]); // 44
})
复制代码
  • Promise.race():只要有一个 Promise 被解决,返回的promise就被解决。实际上,传入该方法的 Promise 会进行竞争,哪个先完成,就返回哪个。
let p1 = new Promise(function(resolve, reject) {
    resolve(42)
})
let p2 = new Promise(function(resolve, reject) {
    resolve(43)
})
let p3 = new Promise(function(resolve, reject) {
    resolve(44)
})
let p4 = Promise.race([p1, p2, p3])
p4.then(function(value){
    console.log(value.isArray()) // false
    console.log(value)  // 42
})
复制代码

异步编程更优解

asyncawait

async代替函数生成器,用await代替 yield 来调度函数。asyn 关键字表示函数以异步模式运行,await 关键字表示调用的函数应该返回一个 Promise。


参考文献

《深入理解ES6》 第11章 Promise 与异步编程

おすすめ

転載: juejin.im/post/7076607182086602765
おすすめ