ES6 Promise

1 简介

Promise 是异步编程的一种解决方案。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。

在控制台打印Promise

可以发现,Promise是一个构造函数,自身有all、race、reject、resolve等方法;
原型prototype上有then、catch等方法(因此只要作为Promise的实例,都可以共享并调用Promise.prototype上面的方法 then,catch)。

2 语法

new Promise(function(resolve,reject){}/*fn*/);

在实例化Promise时需要传入一个函数fn作为参数,并且在Promise构造函数执行时同步执行。
我们先new 一个Promise:

var p = new Promise(function (resolve, reject) {
    console.log('Hello');
    setTimeout(function () {
        console.log(111);
    }, 1000);
})
// 输出: Hello (1s later) 111

上述代码中,执行了一个异步操作,也就是setTimeout,1秒后,输出111。
Promise的构造函数接收一个参数,是一个function(fn入两个参数:resolve,reject,其实这两个参数也是函数,在function执行时被调用。

我们看到2s后在控制台输出相应的结果,这就说明在实例化过程中,作为参数的fn函数也会执行。所以我们用Promise的时候一般是包在一个函数中,在需要的时候去运行这个函数,如:

function fn() {
    return new Promise((resolve, reject) => {
       // code
    });
}
fn()

在fn函数里,会return出Promise对象,也就是说,执行这个函数得到了一个Promise对象。

3 Promise的几种状态

  • pending:初始状态,进行中
  • fulfilled:操作成功
  • rejected:操作失败

Promise对象有以下两个特点:

**(1)对象的状态不受外界影响。**Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。

**(2)一旦状态改变,就不会再变。**Promise对象的状态改变,只有两种可能:

  1. 从pending变为fulfilled
  2. 从pending变为rejected

只要这两种情况发生,状态就凝固了,不会再变了,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。

new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1); // 再调用resolve方法,状态不会再改变到fulfilled了
  }, 0);
  reject(2); // pedding → rejected (状态凝固在rejected了)
}).then((data) => {
  console.log(data);
}, (reason) => {
  console.log(reason);
});
// 输出:2

调用reject方法后,Promise状态变为rejected并且不会再发生改变,此时执行then里面就会执行onrejected操作

4 resolve

当我们在function中调用resolve方法时,Promise的状态就变成fulfilled,即操作成功状态;
当Promise状态为fullfilled时执行then里面相应的操作(执行onfulfilled函数)。
注:then方法里面有两个参数,onfulfilled函数(fulfilled状态时执行) 和onrejected函数(rejected状态时执行)。

var p = new Promise(function (resolve, reject) {
    setTimeout(function () {
        console.log('执行操作1');
        resolve('这是数据1');
    }, 1000);
});
p.then(function (data) {
    console.log(data);
    console.log('这是成功操作');
});
// 输出:
(1s later) 
执行操作1 
这是数据1 
这是成功操作

简单的理解就是调用resolve方法,Promise状态变为fulfilled,执行then方法里面第一个参数onfulfilled函数里的操作。其实then里面的函数就是我们平时所说的回调函数,只不过在这里只是把它分离出来而已。

5 reject

调用reject方法后,Promise状态变为rejected,即操作失败状态,此时执行then里面就会执行onrejected操作:

var p = new Promise(function (resolve, reject) {
    var flag = false;
    if (flag) {
        resolve('这是数据2');
    } else {
        reject('这是数据2');
    }
});
p.then((data)=> { // 状态为fulfilled时执行
    console.log(data);
    console.log('这是成功操作');
}, (reason)=> { // 状态为rejected时执行
    console.log(reason);
    console.log('这是失败的操作');
});
// 输出:
这是数据2 
这是失败的操作

6 catch

Promise中的异常捕获
需求 :前面的Promise执行失败,但是不要影响后续Promise正常执行。
此时可以单独为每个promise通过.then()指定一下失败的回调:(这里以f2为例,只设置了f2)

function f1() {
    return new Promise(function (resolve, reject) {
        setTimeout(() => {
            resolve(1)
        }, 1000)
    })
}
function f2() {
    return new Promise(function (resolve, reject) {
        var flag = false
        setTimeout(() => {
            if (flag) {
                resolve(2)
            }
            if (!flag) {
                reject('error!')
            }
        }, 1000)
    })
}
function f3() {
    return new Promise(function (resolve, reject) {
        setTimeout(() => {
            resolve(3)
        }, 1000)
    })
}
f1().then((data) => {
    console.log(data);
    return f2()
}).then((data) => {
    console.log(data)
    return f3()
}, (reason) => {
    console.log(reason)
    return f3()
}).then((data) => {
    console.log(data)
})
// 输出:(1s later)1 (1s later)error! (1s later) 3

上述代码中,出现错误输出错误信息后,依旧执行了后面的Promise。

其实如果前面的Promise执行失败,后面Promise依赖于前面Promise执行结果的话,那么前面失败了后面也没有继续执行下去的意义了。此时可以使用 .catch()进行异常捕获,只要前面Promise有任何一个执行失败,立即终止所有的Promise的执行,并马上进入catch中去处理Promise中抛出的异常。

catch也是在Promise状态为rejected时执行,then方法捕捉到Promise的状态为rejected的话,就执行catch方法里面的操作。

f1().then((data) => {
    console.log(data);
    return f2()
}).then((data) => {
    console.log(data)
    return f3()
}).then((data) => {
    console.log(data)
}).catch((err) => {
    console.log(err)
})
// 输出:(1s later)1 (1s later)error!
// 此时不再执行f2后面的f3,因为f2依旧出现错误,没有再执行下去的必要了

下面用catch方法改写上面1.5 reject用法里面的例子:

p.then((data) => {
    console.log(data);
    console.log('这是成功操作');
}).catch((reason) => {
    console.log(reason);
    console.log('这是失败的操作');
});

还有,执行resolve的回调时,如果抛出异常(代码出错了),那么并不会报错卡死,而是会进到这个catch方法中:

getNumber()
    .then(function (data) {
        console.log('resolved');
        console.log(data);
        console.log(somedata); //此处的somedata未定义
    })
    .catch(function (reason) {
        console.log('rejected');
        console.log(reason);
    });

上述代码如果进入resolved的回调中,那么输出:

resolved
3  // 随机
rejected
ReferenceError: somedata is not defined

在resolve的回调中,我们console.log(somedata);而somedata这个变量是没有被定义的。如果我们不用Promise,代码运行到这里就直接在控制台报错了,不往下运行了。但是在这里,错误进到catch方法里面去了,而且把错误原因传到了reason参数中。这样一来,即便是有错误的代码也不会报错了。

7 all

Promise的all方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。

Promise
    .all([fn1(), fn2(), fn3()])
    .then(function (results) {
        console.log(results);
    });
// output
异步任务1执行完成
异步任务2执行完成
异步任务3执行完成
[ '数据1', '数据2', '数据3' ]

用Promise.all来执行,all接收一个数组参数,里面的值最终都算返回Promise对象。这样,三个异步操作的并行执行的,等到它们都执行完后才会进到then里面。三个异步操作返回的数据都在then里面,all会把所有异步操作的结果放进一个数组中传给then,就是上面的results。

8 race

// 把fn1、fn2、fn3中的时间分别改为1000,2000,2000
Promise
    .race([fn1(), fn2(), fn3()])
    .then(function (results) {
        console.log(results);
    });
// output
异步任务1执行完成
数据1
异步任务2执行完成
异步任务3执行完成

在then里面的回调开始执行时,fn2()和fn3()并没有停止,仍旧再执行。于是再过1秒后,输出了他们结束的标志。

比如我们可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作:

//请求某个图片资源
function requestImg() {
    var p = new Promise(function (resolve, reject) {
        var img = new Image();
        img.onload = function () {
            resolve(img);
        }
        img.src = 'xxxxxx';
    });
    return p;
}
//延时函数,用于给请求计时
function timeout() {
    var p = new Promise(function (resolve, reject) {
        setTimeout(function () {
            reject('图片请求超时');
        }, 5000);
    });
    return p;
}
Promise
    .race([requestImg(), timeout()])
    .then(function (results) {
        console.log(results);
    })
    .catch(function (reason) {
        console.log(reason);
    });

requestImg函数会异步请求一张图片,我把地址写为"xxxxxx",所以肯定是无法成功请求到的。timeout函数是一个延时5秒的异步操作。我们把这两个返回Promise对象的函数放进race,于是他俩就会赛跑,如果5秒之内图片请求成功了,那么遍进入then方法,执行正常的流程。如果5秒钟图片还未成功返回,那么timeout就跑赢了,则进入catch,报出“图片请求超时”的信息。

9 Promise的作用

首先我们来看这样一个例子,取4个定时器,设置延迟时间都为1s,然后每隔1s依次在控制台输出1 2 3 4的字样:

setTimeout(() => {
    console.log(1);
    setTimeout(() => {
        console.log(2)
        setTimeout(() => {
            console.log(3)
            setTimeout(() => {
                console.log(4)
            }, 1000)
        }, 1000)
    }, 1000)
}, 1000)

上面的问题就是,回调函数的嵌套有点多,使代码的可读性和可维护性都大大降低了。
这时如果我们使用Promise(链式操作)去实现这个效果,就能大大增强其可读性和可维护性:

function f1() {
    return new Promise(function (resolve, reject) {
        setTimeout(() => {
            resolve(1);
        }, 1000)
    })
}
// f2 f3 f4 同f1,省略
f1().then((data) => {
    console.log(data)
    return f2()
}).then((data) => {
    console.log(data)
    return f3()
}).then((data) => {
    console.log(data)
    return f4()
}).then((data) => {
    console.log(data)
})

在这个例子中,将得到Promise实例的过程封装成一个函数(f1~f4)并返回一个Promise实例,再用实例去调用相应的then方法,在每个then方法中通过return得到下一级的Promise实例,然后再去调用then方法执行里面的操作,再返回下一个Promise对象。

也可以这么写,代码量更少:

function fn(n) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(n + 1)
    }, 1000)
  })
}
function f1(n) {
  console.log(n);
  return fn(n)
}
f1(1)
  .then(v => f1(v))
  .then(v => f1(v))
  .then(v => f1(v))

// async/await 方法
async function doit() {
  const t2 = await f1(1)
  const t3 = await f1(t2)
  const t4 = await f1(t3)
  const res = await f1(t4)
}
doit()

实例:Promise封装ajax(这里用setTimeout模拟)

setTimeout(() => {
    console.log(1);
}, 1000)
// 实际开发中一般不会把new Promise直接暴露在外面,而是封装成一个函数,比如这里的fn
function fn() {
    let p = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(1)
        }, 1000)
    });
    return p 
}
fn().then((data) => {
    console.log(data)
})

多个异步操作:

function p(v) {
    return new Promise(function (resolve, reject) {
        setTimeout(() => {
            resolve(v)
        }, v * 1000)
    })
}
Promise.all([
    p(1), p(2), p(3)
]).then(function (result) {
    console.log(result);
})
// (3s later)[1,2,3]

有的时候有这样的需求:后面的ajax请求依赖前面的ajax请求必须按照顺序调动,实现方法就是上面的链式写法

10 注意

Promise是异步的,是指他的then()和catch()方法,Promise本身还是同步的。

setTimeout(()=>{
    console.log(1)
},1000)
new Promise(function (resolve, reject) {
    console.log(2)
    setTimeout(function () {
        resolve();
    }, 2000);
}).then(() => {
    console.log(3);
});
// 输出:2 (1s later) 1 (1s later) 3
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

敲起来blingbling

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值