JavaScript中的异步编程

JavaScript中的异步编程 ---- Promise

了解JavaScript的异步编程之前需要先学习JavaScript的有关运行机制 为了在整体上有个充分的认识,可以先阅读一下知乎上的这篇文章,相对简单便于理解。

# 什么是Promise

Promise最早由社区提出,是用来解决回调地狱的方案,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

所谓的Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。 ---- 《ECMAScript 6 入门》

Promise对象在创建后,就会创建内部状态机,Promise对象代表了一个异步操作,而状态机就包含了这个异步操作的状态,penddingfulfilledrejected, 在Promise对象中,只会存在上述三种状态中的一种,且为不可逆的,不受外界影响。

# 创建以及使用Promise

在ES6中有原生的Promise构造函数,使用关键字new 来创建一个Promise对象。

const promise = new Promise ((resolve,reject) => { // 构造函数接受一个函数为参数,将这个参数暂名为'fn' ,'fn'又接受两个参数,且均为函数
  const flag = Math.random() > .5 ? true : false ;
  if(flag) {
    console.log('使用resolve将promise状态从pending变为resolved');
    resolve('大于0.5')
  } else {
    console.log('使用reject将promise状态从pending变为rejected')
    reject('小于0.5')
  }
})


promise.then((resolve) => {
  console.log(resolve)  // -- '大于0.5'
},(reject) => {
  console.log(reject)  // -- '小于0.5'
})

我们来一点点拆分这个Promise,她接收了一个函数,而这个函数又有两个参数resolvereject,这两个参数都是Promise为我们创建好的函数。这两个函数的作用就是将Promise的状态从pending(等待)转换为resolved(已解决)或者从pending(等待)转换为rejected(已失败)。 而在Promise创建后,有一些自身的方法。这里先只说then,该方法接受两个函数作为参数,这里就和上面传入Promise的构造函数中的参数函数(fn)的两个参数(resolve,reject)对应上了。很显然,then方法的第一个参数函数,接收到resolve传递出的数据,而第二个参数就接收到了reject传递出的数据。 那么Promise是怎么解决异步操作的回调地狱呢?简单的说就是用Promsie对象包裹一个异步操作,异步操作只在Promise对象内部,不受外部影响同时也不阻塞外部,在这个异步操作有了结果后,通过then方法和外部进行数据传递。如下:

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        Math.random() > 0.5 ? resolve('success') : reject('fail');
    }, 1000)
});


promise.then((result) => {
    console.log(result);
}, (err) => {
    console.log(err);
});

这里的setTimeout就是一个异步函数,在1秒后得到结果。若大于0.5的话,resolve就会执行,则Promise的状态从pending变为fulfilled,否则变为rejected,异步操作中的数据(‘success’或‘fail’)会通过函数传递到Promise外部,我们通过then获取到该数据. 那么简单的Promise对象我们已经了解了,但是他是怎么来解决回答地狱的呢?其实很简单,因为then方法会return一个Promise对象来供我们使用.同时我们也可以手动创建新的Promise对象. 举个🌰

const pm1 = new Promise((resolve,reject) => {
  resolve('假装这是异步操作的成功的结果')
});

// @1 使用return 默认的Promise对象
pm1.then(result => {
  console.log('接收Promise传出的:', result);
  return '我是被then return出来的';
}).then(result => {
  console.log("接收上一个then:", result)
  return "我还是被then return出来的";
}).then(result => {
  console.log('接收上一个then:', result)
});

---控制台打印如下
接收Promise传出的: 假装这是异步操作的成功的结果
接收上一个then: 我是被then return出来的
接收上一个then: 我还是被then return出来的
Promise {<resolved>: undefined}
---

// @2 手动创建Promise
pm1.then(result => {
  console.log("pm1异步:",result);
  const pm2 = new Promise((resolve,reject) => {
    setTimeout(() => {
      resolve('pm2异步操作成功的结果')
    },2000)
  });
  return pm2;
}).then(result2 => {
  console.log("pm2异步:",result2)
})

--- 控制台打印如下
pm1异步: 假装这是异步操作的成功的结果
Promise {<pending>}
// 2s后
pm2异步: pm2异步操作成功的结果
---

这就是常见的Promis的链式调用,其实Promise就是原生封装的一个对象,约定创建是传入两个方法分别在成功和失败后调用,又在then的两参数(均为函数)中获取.下面简单介绍一下catch和其他原生方法的使用场景.

# catch

一句话来解释,catch === then(null,reject). catch是用来捕捉Promis中的异常,也就是失败后传递出的信息.then是可以同时接受失败和成功后Promise从内部传出的数据,但是未来方便阅读和维护,一般在then中只执行成功的.我们将整个过程中的异常放到最后,通过catch来捕获.再举个🌰:

const pmCatch = new Promise((resolve,reject) => {
  reject('fail')
});

pmCatch.then(result => {
  console.log('this is then');
  const pmCatch2 = new Promise((resolve,reject) => {
    setTimeout(() => {
      resolve('success')
    },2000)
  });
  return pmCatch2;
}).then(res => {
  cpnsole.log('then:', res);
}).catch(err => {
  console.log('catch:', err);
})

---控制台打印如下
catch: fail
Promise {<resolved>: undefined}
---

看下打印结果,好像跟想象中的不太一样.这里没有打印出‘this is then’和‘then:success’,而是直接打印了‘catch: fail’.明显的在整个Promise链中,只要出现了reject了,整个Promise的状态就会凝固且不会变化.另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。而且Promise对象对于异常有类似冒泡的性质,异常会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。

# Promise.finally()

finally方法用于指定不管Promise对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。


promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

# Promise.all()

Promise.all()方法用于将多个Promise实例,包装成一个新的Promise实例.

const p = Promise.all([p1, p2, p3]); // p1 p2 p3 均为Promise对象

Promise.all()方法的参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例。 p的状态由p1p2p3决定,分成两种情况。

  • 只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。
  • 只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

一般的使用场景是数据同时依赖于多个请求的返回值时.

# Promis.race()

Promise.race()方法同样是将多个Promise实例,包装成一个新的Promise实例。其参数格式同Promise.all();

const p = Promise.all([p1, p2, p3]); // p1 p2 p3 均为Promise对象

只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的回调函数。“行”如其名,竞跑.谁最早跑完听谁的.

# Promise.any()

Promise.any()方法接受一组Promise实例作为参数,包装成一个新的Promise实例。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。

# 其他

其他的还有Promise.resolve(),Promise.reject(),以上两个方法详情参考阮大的《ES6入门》

JavaScript高级程序设计

网络基础

简单了解网络协议等基础网络知识