promise详解

promise是异步编程的一种解决方案,比起传统的解决方案——回调函数和事件——更加的强大。

在项目中,我们经常会遇到异步的问题,在之前,我们通常使用回调函数来解决异步编程,但是在一些比较复杂的情况下,我们经常会陷入回调地狱。promise能够很好的帮助我们解决这个问题。

早就听说过Promise的大名,但是promise到底是个什么东西呢?是个类?对象?数组?函数?来,先给大家拉出来溜溜。
在这里插入图片描述
这里我们可以看到,原来这个promise是一个构造函数,并且在它自己的身上有allrejectresolve这几个特别眼熟的方法,在它的原型上还有thenfinallycatch等方法,也就是说,我们在用Promise new实例化一个对象时,这个对象一定有thenfinallycatch这些方法。

【Promise的生命周期】

promise对象有三个状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。
每个Promise都会经历一个短暂的生命周期:先是处于进行中(pending)的状态,此时操作尚未完成,操作结束后,Promise可能会进入到以下两个状态中的其中一个。
假如异步操作成功完成,进入(fulfilled)状态;假如异步操作没有成功,进入(rejected)状态。

注意:promise有一个特点,就是一旦状态改变,就不会再改变,Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。这个时候,再给promise添加回调函数就会立即调用,这一点就和事件回调很不一样,事件回调是在错过事件之后再去监听是无法得到结果的。

【应用】

我们先来试一下,改写一个普通方法变为一个promise实例,更加方便大家的理解。
原方法如下:

export function getInfo () {
  return '返回一个字符串'
}

我们在调用的时候:

// 原方法调用如下
const str = getInfo()
console.log(str)

修改成promise实例之后:

export function getInfo () {
  return new Promise((resolve, reject) => {
    const str = '返回一个字符串'
    if (str === undefined) {
      //抛出异常
      reject(new Error('异常:空字符串'))
    }
    //继续执行
    resolve(str)
  })
}
// 方法调用如下
getInfo.then(str => {
	console.log(str)
})

【基本用法】

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

我们可以看到promise接收了一个函数作为参数,这个函数有两个参数,分别是resolve和reject,他们是js引擎自己提供的,不用我们来配置。

resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

promise实例生成之后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

then方法接受了两个参数,第一个参数是当promise被resolve之后执行的方法,第二个参数是promise被rejected之后执行的方法。这两个参数都是可选的,不一定都要写,他们两个都可以接受promise传递出来的参数。

【promise在创建之后就会立即执行】(这也是为什么我们经常将promise作为一个函数的返回值来使用)

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hahahahah');

// Promise
// Hahahahah
// resolved

上面的代码中,promise对象创建之后立即执行,所以先输出promise,但是resolve指定的回调函数会在当前脚本的同步任务全部执行完之后再执行,所以会先输出Hahahahah,最后输出resolved。
【关于promise传递给回调函数的参数】
我们知道,在promise执行完之后可以给回调函数传递参数,如果是rejected,一般会给回调函数传递一个错误的实例;如果是resolve,除了传递一般的普通的正常值以外,有时候我们还能看到传递另一个promise实例参数的情况。

const p1 = new Promise(function (resolve, reject) {
  // ...
});

const p2 = new Promise(function (resolve, reject) {
  // ...
  resolve(p1);
})

上面代码中,p1和p2都是 Promise 的实例,但是p2的resolve方法将p1作为参数,即一个异步操作的结果是返回另一个异步操作。

注意,这时p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行。

给大家看一个实例:

const p1 = new Promise(function (resolve, reject) {
  setTimeout(() => reject(new Error('fail')), 3000)
})

const p2 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(p1), 1000)
})

p2
  .then(result => console.log(result))
  .catch(error => console.log(error))
// Error: fail

上面代码中,p1是一个 Promise,3 秒之后变为rejected。p2的状态在 1 秒之后改变,resolve方法返回的是p1。由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句都变成针对后者(p1)。又过了 2 秒,p1变为rejected,导致触发catch方法指定的回调函数。

【常用方法】

【Promise.prototype.then()】
从表面上看,Promise只是能够简化层层回调的写法,而实质上,Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数能够及时调用,它比传递callback函数要简单、灵活的多。所以使用Promise的正确场景是这样的:

runAsync1()
.then(function(data){
    console.log(data);
    return runAsync2();
})
.then(function(data){
    console.log(data);
    return runAsync3();
})
.then(function(data){
    console.log(data);
});

这样采用then的链式用法,就解决了回调地狱的问题。
【Promise.prototype.catch()】
Promise.prototype.catch()用于在promise返回错误时的回调函数

getJSON('/posts.json').then(function(posts) {
  // ...
}).catch(function(error) {
  // 处理 getJSON 和 前一个回调函数运行时发生的错误
  console.log('发生错误!', error);
});

这个时候我们有问题了,我们的then不是也有这样的作用吗?then()的第二个参数就是promise执行失败的时候的回调函数啊。

// bad
promise
  .then(function(data) {
    // success
  }, function(err) {
    // error
  });

// good
promise
  .then(function(data) { //cb
    // success
  })
  .catch(function(err) {
    // error
  });

第二种写法要好于第一种写法,理由是第二种写法可以捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)。因此,建议总是使用catch()方法,而不使用then()方法的第二个参数。
【Promise.prototype.finally()】

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

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

const p = Promise.all([p1, p2, p3]);

p的状态由p1、p2、p3决定,分成两种情况。

(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

【Promise.race() 】
Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.race([p1, p2, p3]);

上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值