阅读 59

ES6的Promise对象详解

什么是Promise?为什么要使用Promise?

当我们使用js的异步调用时通常习惯使用回调函数,这样的代码简单便于理解,但是当回调嵌套的时候就会造成代码混乱,不好梳理,比如:

function fun1(arg1,function(){
// dosomething 
})
复制代码

这样简单的嵌套自然是没问题,还便于理解。但是当有多层嵌套的时候: 我们只知道Promise是最大的好处是为了避免“回调地狱”,就是多层的回调

function fun1(arg1,function(data1){
    function fun1(arg2, function(data2){
        function fun1(arg3, function(data3){
            //.....
        })
    })
})
复制代码

像上面这样,三层嵌套就已经很麻烦了,更何况更多层的嵌套,所以为了让回调更明显,代码更容易理解,使用Promise可以很优雅的解决这个问题:

var p = new Promise(function(resolve,reject){ }).then(function(data){ }).then(function(data){ }).then……

上面就是Promise的基本用法,Promise接受一个回调函数,回调函数接收两个参数,**resolve(将Promise的状态从pending变为fulfilled,在异步操作成功时调用,并将异步操作的结果传递出去)、reject(将Promise的状态从pending变为rejected,在异步操作失败时调用,将异步操作的错误作为参数传递出去)**这两个都是函数,表示成功和失败的处理函数。then中接受的是上一次回调返回的结果,所以这样的链式调用就可以完全清晰的实现多层调用。

Promise对象有两个特点: ①,对象的状态不受外界的影响,Promise有三种状态:Pending(进行中)、fulfilled(已成功)、rejected(失败),只用异步操作的结果可以决定当前是哪一种状态,其他任何操作都无法改变这个操作。 ②.一旦状态改变之后就不会再改变。任何时候都可以得到这个结果。状态变化只有两种可能:从pending到fulfilled和从pending到rejected。只要改变就已经定型了。

Promise的缺点: ① 一旦创建就无法取消,一旦新建就会立即执行 ② 如果不设置回调函数,它的内部错误就不会反映到外部。 ③ 当处于pending状态时,无法判断进展到哪一阶段(刚开始还是快完成)。

接下来讲一下Promise的基本语法,先看一下Promise打印出来的结果

从上图可以看到Promise.prototype上有catch、then、constructor方法。所以这几个方法可以被实例继承。

Promise自身会有Promise.all()、Promise.race()、Promise.resolve()、Promise.reject()一些常用的方法。

1.Promise.prototype.then()

let promise = new Promise(function(resolve,reject){
        console.log("promise");
        resolve();
});
setTimeout(function(){
    console.log("setTimeout");
},0)
promise.then(function(){
        console.log("resolved");
})
console.log("hi");

// promise   hi   resolved  setTimeout
复制代码

上面的代码很好的验证了,promise是创建之后立即执行,then方法指定的脚本在当前的所有同步任务完成之后再执行,setTimeout是在下一轮“时间循环”开始时执行,then在本轮事件循环结束时执行。

2.Promise.prototype.catch() 当Promise对象执行成功时使用的是resolve回调函数,进而在then方法中进一步处理,当promise对象失败时在那儿进行处理? 有两种方法:①在then方法接受第二个函数参数,用来处理错误。(不推荐) ② 在catch中进行处理。(推荐) 先看第一种:

let promise = new Promise(function(resolve,reject){
        reject();
});

promise.then(function(){
        console.log("resolved");
},function(){
    console.log("rejected")
})

输出 rejected
复制代码

使用catch

let promise = new Promise(function(resolve,reject){
        reject();
});

promise.then(function(){
        console.log("resolved");
}).catch(function(){
    console.log("catch the reject")
})

输出 catch the reject
复制代码

reject的作用就相当于抛出错误,catch或者then的第二个函数参数进行捕获,再resolve之后再抛出错误是没有用的,因为状态一旦发生就无法改变。

Promise对象的错误具有冒泡的性质,即所有的错误一直可以向后传递,知道遇到cantch被捕获。 注意:一般尽量不要使用then的第二个函数参数进行错误处理,尽量使用catch进行错误的统一处理。

Promise对象若没有指定错误处理,内部错误不会退出进程或终止脚本执行,也就是说promise对象的内部错误不会影响外部代码的执行。

let promise = new Promise(function(resolve,reject){
        resolve();
});

promise.then(function(){
        console.log("resolved");
        y+2;
})

setTimeout(function() {
            console.log("我出现在y+2之后")
},3000)
上面的代码,浏览器遇到y+2未声明抛出错误,但是setTimeout中的字符串在3秒后依然可以打印出来。
复制代码

看一下Promise对象的一些方法: 1. Promise.resolve() 我觉得介绍Promise的方法时应该先介绍这个方法,因为后面要用到。 不要觉得它生成的Promise对象的状态直接是resolved。(视情况而定) 该方法的作用是将现有对象转化为一个Promise对象。 将一个对象转化为Promise对象分为四种情况: ① 参数是Promise的实例,不做任何改变。 ② 参数是一个对象,且含有then方法(简称thenable对象);

var thenable = {
        then:function(resolve,rejected){
                resolve(42);
    }
}

let p1 = Promise.resolve(thenable);
p1.then(function(data){
    console.log(data);//42
})
复制代码

thanable的then方法执行后,对象p1的状态就变为resolved,立即执行then。 当然Promise.resolve()也可以生成状态为rejected的promise对象,上面只需要将resolve改为reject,再在p1.then后面加上.catch用于捕获错误就可以啦! ③ 参数不具有then方法,或者说根本就不是对象的时候。 var p = Promise.resolve(“Hello”); p.then(function(s){ console.log(s);//Hello }) 返回的promise的实例的状态直接就是resolved,所以会执行then方法。并且Promise.resolve()的参数会传递给回调函数。

④ 不带有任何参数,用于快速的生成一个Promise对象。 var p = Promise.resolve();

2. Promise.all() 该方法接受多个Promise实例作为参数,返回一个新的Promise实例。 当所有的Promise实例都返回resolve的时候,新的Promise实例的状态是fulfilled,此时p1,p2,p3的返回值组成一个数组传递给新实例的回调函数。当有一个返回的是rejecte的时候,新实例的状态就是rejected。此时第一个返回reject的实例的返回值就会传递给p的回调函数。

promise接受的参数都是promise的实例,那么怎样将所有的参数都转化为promise的实例呢?使用上面的Promise.resolve()方法。 为了更好的掌握Promise.all()方法,来做一个例题。

怎样使用Promise的相关知识输出 Welcome To XIAN

var p1 = Promise.resolve("Welcome");
var p2 = "To";
var p3 = new Promise(function(resolve,reject){
    setTimeout(function(){
        resolve("XIAN");
    },1000)
})
复制代码

其实这个题在我讲的这块出现,大家都知道要使用Promise.all方法,还有问题就是他的参数必须都是Promise的实例,p1已经通过Promise.resolve转化成了Promise对象,p2我们再使用它转换一下,p3本身就是Promise对象的实例。

使用下面的代码就完美的解决了:

Promise.all([p1,Promise.resolve(p2),p3])
       .then(function(data){

        console.log(data instanceof Array)//true
        console.log(data.join(" "));//转化为字符串输出

}).catch(function(e){
    console.log(new Error(e));
})
复制代码

上面的data是Promise对象resolve函数的参数组成的数组。还有all中的顺序决定了输出的顺序,与其他的因素没有关系。

如果所有实例中有catch方法用于捕获错误,则使用Promise.all方法的catch是不会捕获到的。但是会执行Promise.all中的then方法,为什么?不是说当所有实例都返回的是resolve状态时才会触发Promise.all的then方法么?

因为当某一个实例报错时,使用catch进行错误处理,返回的是一个新的Promise实例,该实例完成catch之后状态也会变为resolve,所以导致Promise.all所有实例的都返回resolve,会触发Promise.all的then。 解决方法就是在实例中不添加catch,那么实例中reject就会触发Promise.all的catch,从而达到Promise.all存在的真正意义。

3.Promise.race() 与Promise.all一样,接受的是promise实例数组作为参数,新生成一个新的Promise对象。所以该方法的参数可以使用Promise.resolve()方法来解决。

该方法的Promise的对象由第一个返回reject或者resolve的实例的状态决定。可以使用我们经常看到的是图片加载超时什么提示,那么可以使用该方法实现一下。 加载图片的函数也使用Promise对象

function preloadImage(path){
    return new Promise(function(resolve,reject){
        var image = new Image();
        image.onload = resolve;
        image.onerror = reject;
        image.src = path;
    })
}
复制代码

要实现的功能是在五秒之后如果图片加载不出来就提醒图片加载超时。

Promise.race([preloadImage("/images/1.png"),
            new Promise(function(resolve,reject){
                setTimeout(function() {
                    reject();
                },5000);
    })]).then(function(){
        alert("图片加载成功!")
    }).catch(function(){
    alert("图片加载失败!")
    })
复制代码

在五秒中之内先是图片加载,(当然图片加载也有可能失败)还是在五秒后出发setTimeout函数执行reject。这样可以允许图片在五秒之内完成加载给提示。

4.Promise.reject() 看到这个就会想到Promise.resolve().区别就是: Promise.resolve()生成的Promise对象可以是rejected状态和resolve状态。 Promise.reject()只能生成状态为rejected的Promise实例。

var arg = "Hello";
var pro1 = Promise.reject(arg);

pro1.then(function(){
    console.log("resolved");
}).catch(function(e){
    console.log(e === arg)//true
    console.log(e) //Hello
})
复制代码
var thenable = {
    then(resolve,reject){
        reject("出错了");
    }
}
var pro1 = Promise.reject(thenable);

pro1.then(function(){
    console.log("resolved");
}).catch(function(e){
    console.log(e === thenable) //true
    conaloe.log(e);
    //{then: ƒ}
    //then:ƒ then(resolve,reject)
    //__proto__:Object
    console.log(e instanceof Object); //true
})
复制代码

可以看出Promise.reject()方法的参数会原封不动的作为reject的理由,编程后续方法的参数。**所以这里输出的e不是“出错了”字符串,而是传入Promise.reject()中的参数。它不是像Promise.resolve()一样使用resolve或者reject中的参数。

文章分类
前端
文章标签