聊聊promise系列(基础)

1,482 阅读7分钟

前言

本文将从浅到深的去剖析promise。由于内容较多,分为上下两篇。

内容大纲

  • 异步编程的解决方案(完成)
  • promise是什么(完成)
  • promise的用法(完成)
  • promise的优点与缺点(完成)
  • promise的错误捕获(待续)
  • promise的原理(待续)
  • promise的底层源码(待续)
  • 自己实现一个promise(待续)

异步编程的解决方案

js是一门单线程的语言,所以其中会涉及到很多异步的操作,异步编程的解决方案有很多种,我们主要讲一种最基础的和这篇文章的主角(promise):

回调函数

在异步编程中,这种的应用范围最广,举个定时器的例子:

setTimeout(() => {
    // ...
}, 1000);

当然在业务中异步请求也会用到很多,但当多个异步操作需要串行操作的时候,就会有回调地狱产生。

$.get(url, data1 => {
    console.log(data1)
    $.get(data1.url, data2 => {
        console.log(data2)
        $.get(data2.url, data3 => {
            console.log(data3)
        })
    })
})

代码没有美感,且不利于维护。当然,我们可以通过减少代码嵌套,模块化等手段来修复。但是并不如下面的解决方案优雅。

佼佼者——promise

const reqMethod = (url) => {
    return new Promise((reslove, reject) => {
        $.get(url, data => {
            if(data.success) {
                reslove();
            } else {
                reject();
            }
        })
    })
}

reqMethod(url).then((data1) => {
    return reqMethod(data1.url);
}).then((data2) => {
    return reqMethod(data2.url);
}).then((data3) => {
    return reqMethod(data3.url);
})

这样的实现方式,符合易于阅读,因为每一步操作都是按照先后顺序进行的。

promise是什么

Promise从不同角度理解,有很多种含义。

promise的一种承诺

promise从字面意思理解,就是许诺和承诺的意思,对于一种承诺而言,有三种状态:

1、承诺还未达成,还在纠结过程中(pending状态)
2、承诺没有实现,失言了(rejected状态)
3、承诺实现了,就是成功的状态(fulfilled状态)

Promise是一种标准的规范

在这里不过多展开,大家可以去看Promise/A+ 规范或者ECMAscript规范;

Promise是ES6提供的一种对象

Promise是一个对象,是一个构造函数,ES6将其写进了语言标准。统一了用法。最基础的用法如下:

const promiseMethod = new Promise((resolve, reject) => {
    // some code
    if(/*异步成功的条件*/) {
        resolve();
    } else {
        reject();
    }
})

Promise的用法

new Promise

Promise是一个构造函数,最基础的作用就是用new操作符生成一个实例对象

const promiseMethod = new Promise((resolve, reject) => {
    // some code
    if(/*异步成功的条件*/) {
        resolve();
    } else {
        reject();
    }
})

Promise可接受的参数是一个函数,resolvereject是该函数的两个参数,由js引擎提供,不需要自己定义。

resolve的作用是将pending状态变更为fulfilled状态。

reject的作用是将pending状态变更为rejected状态。

Promise新建时就会立即执行,与何时调用无关,与结果也无关

举个例子

const promiseOne = new Promise((resolve, reject) => {
    console.log('has finish');
    resolve();
})

1、console.log在新建过程中就执行了。

2、promiseOne的结果已经固定下来了,无论何时调用,结果都不会发生改变。

Promise.prototype.then

then方法是被定义在Promise的原型上,作用是:添加Promise状态改变后的回调函数。

then方法接收两个参数,第一个参数是resolve状态执行的函数,第二个参数是reject执行的函数。

then方法返回的是一个新的Promise对象。

举个例子:

const promiseOne = new Promise((resolve, reject) => {
    console.log('has finish');
    resolve();
})

const promiseTwo = new Promise((resolve, reject) => {
    console.log('has reject');
    reject();
})

const promiseThree = promiseOne.then(() => {
    console.log('成功的回调')
}, () => {
    console.log('失败的回调')
})

promiseTwo.then(() => {
    console.log('成功的回调')
}, () => {
    console.log('失败的回调')
})

console.log(promiseThree);

输出的结果:

has finish
has reject
<Promise>(pending)
成功的回调
失败的回调

Promise.prototype.catch

catch方法其实和then第二个回调函数的别名,作用是用于储物发生时的回调处理。

catch捕获两类错误:

1、异步操作时抛出错误,导致状态变为reject

2、then回调过程中产生的错误。

举个例子:

//第一种情况:异步操作过程中报错
const promise = new Promise(function(resolve, reject) {
  throw new Error('test');
});
promise.catch(function(error) {
  console.log(error);
});

// 第二种情况:then执行过程中报错

const promise = new Promise((resolve, reject) => {
    resolve();
})

promise.then(() => {
    throw new Error('test');
}).catch(function(error) {
  console.log(error);
});

关于catchthen第二个参数的区别:

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

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

第二种写法的优点在于,then执行过程中的报错,catch一样能捕获,优于第一种写法。

更深入的错误捕获我们单独放一章讲。

Promise.prototype.finally

finally方法作用在于不管promise返回的状态是什么,它都会在执行。

finally不接受任何参数。

promise
  .then(function(data) {
    // success
  })
  .catch(function(err) {
    // error
  }).finally(() => {
      
  });

finally中执行的状态与promise的结果无关,而且在方法中无法得知promise的运行结果。

Promise.all

用法:

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

作用:是将多个promise示例封装成一个promise实例。结果只有以下两种情形:

  • 全部成功

所有promise都成功,总的promise才会成功

const p1 = new Promise((resolve, reject) => {
    resolve('hello');
})
const p2 = new Promise((resolve, reject) => {
    resolve('hello');
})
Promise.all([p1, p2]).then(() => {
    console.log('全部成功')
}).catch(() => {
    console.log('全部失败')
})

// 全部成功
  • 全部失败

只要有一个promise的状态从pending变成reject就是失败。

const p1 = new Promise((resolve, reject) => {
    resolve();
})
const p2 = new Promise((resolve, reject) => {
    reject();
})
Promise.all([p1, p2]).then(() => {
    console.log('全部成功')
}).catch(() => {
    console.log('全部失败')
})

Promise的时间如何计算?

const newDate = new Date();
const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve();
    }, 500)
})
const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve();
    }, 1000)
})
Promise.all([p1, p2]).then(() => {
    const now = new Date();
    let time = now.getTime() - newDate.getTime();
    console.log(time);
})
// 1001

按最长的那个为准。

Promise.race

用法:

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

作用:是将多个promise示例封装成一个promise实例。

Promise.race与Promise.all的区别

Promise.race的状态取决于最先完成的promise的状态。

举个例子,我们需要对一个请求做5秒的timeout,就可以用Promise.race

const reqMethod = (url) => {
    return new Promise((reslove, reject) => {
        $.get(url, data => {
            reslove();
        })
    })
}

const timeout = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('timeout')
    }, 5000)
})
Promise.race([reqMethod(xxx),timeout]).then(() => {
    console.log('请求成功')
}).catch(() => {
    console.log('timeout')
})

Promise.resolve

作用:需要将现有的对象转化为promise对象。

Promise.resolve({});
//等价于
new Promise(resolve => resolve({}));

Promise.resolve会根据传入的不同参数做不同的处理

  • 传入一个promise对象

不做任何操作,原封不动的返回该对象。

  • 参数是一个thenable对象

立即执行thenable对象中的then方法,然后返回一个resolved状态的promise

let thenable = {
    then: function(resolve, reject) {
        resolve('success')
    }
}

const p = Promise.resolve(thenable);
p.then((e) => {
    console.log(e);
})
  • 不是对象

直接返回一个resolved状态的promise。并将参数带给回调函数。

const p = Promise.resolve('参数');

p.then((e) => {
    console.log(e)
})
  • 不传参数

直接返回一个resolved状态的promise

Promise.reject

其作用是返回一个新的promise实例,状态直接为reject

Promise.reject('error');
//等价于
new Promise((resolve, reject) => reject('error'));

不同于Promise.resolve,其参数会原封不动的作为reject的理由。

举个例子:

const p = Promise.reject('error');
p.catch((e) => {
    console.log(e)
})
// error

Promise的优缺点

Promise的特点

  • 状态不受外界影响,只满足于结果
1、promise代表一个异步操作,一共有三种状态:pending、fulfilled和rejected。
2、promise的结果只服从于异步操作的结果,成功进入fulfilled状态,失败进入rejected状态。
  • 状态变更之后不会在改变
1、promise状态变化只有两种可能,一种从pending到fulfilled,或者是从pending到reject。
2、当状态变更完时,状态将不在发生改变。

Promise的优点

  • 链式写法可读性比回调函数的写法更优雅。
  • 与回调函数相比更加方便错误处理。
  • 与事件体系相比,一次性返回结果,更加适用于一次性返回的结果。

Promise的缺点

  • 不能取消执行过程。
  • 不能读取异步过程中的进度。
  • 生成之后对象结果不会改变。

结语

本文对promise的用法进行了详解,之后会更新两篇深入一点的文章:

  • promise错误捕捉
  • 聊聊promise系列(深入)