javascript中Promise

1.Promise是什么?

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

2. 基本用法

Promise对象是一个构造函数,用来生成Promise实例。下面我们来

1.创建一个promise实例:

const promise = new Promise(function(resolve, reject) {
    // ... some code
    if (/* 异步操作成功 */){
        resolve(value);
    } else {
        reject(error);
    }
});
  • Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
  • resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 fulfilled),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
  • reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

2.使用promise实例

Promise实例生成以后,可以用then方法分别指定fulfilled状态和rejected状态的回调函数,从而接收传递过来的状态。

promise.then(function(value) {
    // success
}, function(error) {
    // failure
});
  • then方法可以接受两个回调函数作为参数。
  • 第一个回调函数是Promise对象的状态变为fulfilled时调用
  • 第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供
  • 这两个函数都接受Promise对象传出的值作为参数。

3. 特点

从上面的用法看,我们可以了解到Promise有如下特点:

  1. Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有调用了resolve/reject函数来处理结果,才可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

举个例子:

var get = new Promise(function (resolve, reject) {
    console.log(1);
    resolve(2)
    console.log(3)
    setTimeout(function(){
        console.log(5)
    },1);
})
get.then(function (value) {
    console.log(value);
})
console.log(4);

//输出结果为:1 3 4 2 5

从上面例子我们可以看出,只要使用new Promise,相当于创建一个异步操作。只有调用resolve或者reject才能触发异步操作。所以我们一般在promise内放置异步操作(如请求等),当返回结果,调用resolve或者reject。这里为了简洁明了说明其是异步,所以没有放异步操作,直接调用了resolve函数,从打印结果就可以看出其是异步操作。

  1. Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

我们更改上面例子:

var get = new Promise(function (resolve, reject) {
    console.log(1);
    resolve(2)
    reject(5)
    console.log(3)
})
get.then(function (value) {
    console.log(value);
},function(error){
    console.log(error);
})
console.log(4);

//输出结果仍为:1 3 4 2

从例子结果可以看出,promise对象要么是fulfilled,要么是rejected,所以不能同时输出

  1. 一般来说,调用resolve或reject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。

    new Promise((resolve, reject) => {
    return resolve(1);
    // 后面的语句不会执行
    console.log(2);
    })

下面是一个用Promise对象实现的 Ajax 操作的例子。

const getJSON = function(url) {
    const promise = new Promise(function(resolve, reject){
        const handler = function() {
            if (this.readyState !== 4) {
                return;
            }
            if (this.status === 200) {
                resolve(this.response);
            } else {
                reject(new Error(this.statusText));
            }
        };
        const client = new XMLHttpRequest();
        client.open("GET", url);
        client.onreadystatechange = handler;
        client.responseType = "json";
        client.setRequestHeader("Accept", "application/json");
        client.send();

    });
    return promise;
};

getJSON("/posts.json").then(function(json) {
    console.log('Contents: ' + json);
}, function(error) {
    console.error('出错了', error);
});

4. Promise.prototype.then()

Promise 实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态(已定型,指的是fulfilled状态))的回调函数,第二个参数(可选)是rejected状态的回调函数。

上面我们讲过 回调地域问题,现在我们用promise进行链式调用来解决该问题

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

request(url).then(data1 => {
    return request(data1.url);   
}).then(data2 => {
    return request(data2.url);
}).then(data3 => {
    console.log(data3);
})

从代码上看,是不是更直观更简洁。

5. Promise.prototype.catch

Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。我们分别讲解then函数和catch函数,对比两者的区别:

5.1 then函数的第二个参数:错误回调rejection

1.当调用reject函数时候,执行rejection回调

var get = new Promise(function (resolve, reject) {
    reject(a)
})
get.then(null, function (error) {
    console.log(error); //1
})

运行结果如下:

20190925150628.png

2.当程序运行错误,执行rejection回调

var get = new Promise(function (resolve, reject) {
      console.log(a);
})
get.then(null, function (error) {
    console.log(error); 
})

运行结果和上面一样:


20190925150628.png

由上面2个例子我们可以知道,reject等同于抛出错误。供后面回调函数捕获错误。

2.Promise 内部的错误不会影响到 Promise 外部的代码运行,通俗的说法就是“Promise 会吃掉错误”(js不会因为错误崩溃导致下面代码无法运行)。

var get = new Promise(function (resolve, reject) {
    reject(a)
})
console.log(1);
setTimeout(function(){
    console.log(2)
},1);

运行结果如下:

20190925144458.png

从结果可以看出,我们并没有使用回调函数来捕获错误,所以浏览器会报错,但是并没有终止脚本运行。

3.Promise 的状态一旦改变,就永久保持该状态,不会再变了。所以在resolve语句后面,再抛出错误,不会被捕获

var get = new Promise(function (resolve, reject) {
    resolve('ok')
    console.log(a)
    console.log(3)
})
get.then(null,function(error){
    console.log(error); //没有执行
})
console.log(1);
setTimeout(function(){
    console.log(2)
},1);

// 1 2

从上面例子可以看出,在resolve('ok')后面调用未定义变量a抛出错误,回调函数并没有执行捕获。在这有个奇怪的问题,上面我们在讲解promise特点时候,举过例子证明resolve后面语句也会执行,这里为什么打印结果不是 3 1 2?

这里需要注意的是:由于resolve后面抛出错误,所以后面语句会被阻止运行(同步异步都会被阻止)。如果没有抛出错误,则正常运行。

5.2 catch函数

上面讲了then函数的第二个参数:我们知道了如果在promise中如果有错,可以使用错误回调rejection进行捕获。那catch呢?和rejection一样,也一样可以捕获。

var get = new Promise(function (resolve, reject) {
    reject(a)
})
get.catch(function (error) {
    console.log(error); //1
})

运行结果如下:

20190925150628.png

那么他们两个到底有什么区别呢?唯一的区别就是,如果在 then 的第一个成功回调函数里抛出了异常,catch 能捕获到,而错误回调函数捕获不到

var get = new Promise(function (resolve, reject) {
    resolve('ok')
})
get.then(function (value) {
    console.log(a);
    console.log(value); //因报错被阻止运行
},function(error){
    console.log(error); //没有执行捕获,所以浏览器报错
})

所以运行结果如下:

20190925151125.png

现在我们改成catch就可以捕获

var get = new Promise(function (resolve, reject) {
    resolve('ok')
})
get.then(function (value) {
    console.log(a);
    console.log(value); //因报错被阻止运行
}).catch(function(error){
    console.log(error); //可以捕获
})

运行结果如下:

20190925151248.png

所以一般总是建议,不用rejection回调函数来捕获错误,用catch方法,这样既可以处理 Promise 内部发生的错误,又可以处理成功回调函数中的错误。

有的人可能会好奇,那假若都有错误怎么办?

var get = new Promise(function (resolve, reject) {
  console.log(e);
  resolve('ok')  //报错所以直接进入捕获函数,因为只可能有一种状态
  console.log(1); //因报错被阻止运行
})
get.then(function (value) {
  console.log(value); //不会调用
}).catch(function(error){
    console.log(error);
})

运行结果如下:


20190925151924.png

Promise对象的状态改变,只可能有一种结果,要么成功,要么失败,不能改变,因为上面直接报错,所以直接进入catch函数中。这里只以catch举例,实际此时用catch或者rejection回调函数都可以。

注意:如果同时用rejection回调函数和catch函数捕获错误,只会优先执行rejection回调函数,不会执行catch函数。

6. Promise.prototype.finally

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

var get = new Promise(function (resolve, reject) {
  resolve(1)  //报错所以直接进入捕获函数,因为只可能有一种状态
})
get.then(function (value) {
  console.log(value); //不会调用
}).catch(function(error){
    console.log(error);
}).finally(function(){
    console.log(2);
})
console.log(3);
setTimeout(() => {
    console.log(4);
}, 0);

// 3 1 2 4

这里只举了成功状态,失败状态一样,就不举例了。

7. Promise.resolve(value)

该方法的作用是:把value转换成Promise对象。这里说的Promise对象指的是由Promise构造函数生成的实例。

如果传入不同类型的 value 值,返回结果也有区别:

  1. value本身就是Promise对象,返回结果和入参value相同
let get = new Promise(function (resolve, reject) {
    resolve(1) 
})
let p1= Promise.resolve(get)
console.log(p1===get); //true
  1. value是个thenable对象(即该对象有属性名字为then方法),返回结果为Promise对象,其跟随 thenable 对象中的then函数状态(resolved/rejected)
//demo1
let thenable = {
    then: function (resolve, reject) {
        resolve(1);
    }
};
let p1 = Promise.resolve(thenable)
p1.then(function(value){
    console.log(value); //1
})

//demo2
let thenable = {
    then: function (resolve, reject) {
        reject(2);
    }
};
let p1 = Promise.resolve(thenable)
p1.then(null,function(error){
    console.log(error) //2
})

我们平常用的jquery的ajax就是thenable对象,我们打印看下:

 console.log($.ajax());

打印结果如下图:

20190925175951.png
  1. value不是具有then方法的对象,或根本就不是对象如字符串、数值等,返回结果为一个resolved状态的 Promise 对象
const p = Promise.resolve('Hello');
p.then(function (s){
    console.log(s)
});
console.log(p)

打印结果如下图:

20190926110002.png

所以,假如我们用错误回调函数接收,是不会执行的。

const p = Promise.resolve('Hello');
p.then(null,function (error){
    console.log(error)
});
console.log(p);

打印结果如下图:

20190926110359.png
  1. value不传,返回结果为一个resolved状态的 Promise 对象
 const p = Promise.resolve();
 p.then(function (s){
     console.log(s)
 });
 console.log(p);

打印结果如下图:

20190926111545.png

8. Promise.reject(value)

该方法的作用是:把value转换成Promise对象。这里说的Promise对象指的是由Promise构造函数生成的实例。

如果传入不同类型的 value 值,返回结果都为一个rejected状态的 Promise实例(和上面讲的resolve方法不一样)。所以调用该实例会直接调用catch方法或者then第二个参数,其值就是传进去的value值

  1. value是promise对象
let get = new Promise(function (resolve, reject) {
    resolve(1) 
})
let p1= Promise.reject(get)
p1.catch(function(value){
    console.log(value===get); //true
})

注意,如果value为promise对象,该对象里面不能用reject或者语法错误,具体原因暂不清楚。

let get = new Promise(function (resolve, reject) {
    reject(1) 
})
let p1= Promise.reject(get)
p1.catch(function(value){
    console.log(value===get);
})
20190926162816.png
  1. value是个thenable对象
let thenable = {
    then: function (resolve, reject) {
        resolve(1);
    }
};
let p1 = Promise.resolve(thenable)
p1.catch(function(value){
    console.log(value===thenable); //true
})
  1. value不是具有then方法的对象,或根本就不是对象如字符串、数值等
const p = Promise.reject('Hello');
p.catch(function (s){
    console.log(s)
}); //Hello
  1. value不传
let p1 = Promise.reject()
p1.catch(function(value){
    console.log(value);
})
console.log(1) 
//1 undefined

注意,如果不传并且不catch捕获,会报错。比如下面代码:

 let p1 = Promise.reject()
 console.log(1) 

运行结果如下:

20190926163107.png

9. Promise.all(arr)

Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。参数为数组。用法如下:

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

上面代码中,Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用之前讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。

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

  • 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled。此时p1、p2、p3的返回值组成一个数组,传递给p的then回调函数。
  • 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的错误回调函数。

10. Promise.race(arr)

Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。参数为数组。用法如下:

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

上面代码中,Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用之前讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。

和all方法不同的是:

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

参考来源:

《ES6标准入门——阮一峰》
面试精选之Promise

推荐阅读更多精彩内容