资讯专栏INFORMATION COLUMN

js-Promise

xcold / 1839人阅读

摘要:总结用方法创建对象用或添加对象的处理函数它的作用是为实例添加状态改变时的回调函数。方法是的别名,用于指定发生错误时的回调函数。

一、为什么需要Promise

Javascript 采用回调函数(callback)来处理异步编程。从同步编程到异步回调编程有一个适应的过程,但是如果出现多层回调嵌套,也就是我们常说的回调金字塔(Pyramid of Doom),绝对是一种糟糕的编程体验。于是便有了 Promises/A , Promises/A +等规范,用于解决回调金字塔问题。

    // 回调金字塔
    request("test1.html", "", function(data1) {
        console.log("第一次请求成功, 这是返回的数据:", data1);
        request("test2.html", data1, function (data2) {
            console.log("第二次请求成功, 这是返回的数据:", data2);
            request("test3.html", data2, function (data3) {
                console.log("第三次请求成功, 这是返回的数据:", data3);
                //request... 继续请求
            }, function(error3) {
                console.log("第三次请求失败, 这是失败信息:", error3);
            });
        }, function(error2) {
            console.log("第二次请求失败, 这是失败信息:", error2);
        });
    }, function(error1) {
        console.log("第一次请求失败, 这是失败信息:", error1);
    });
    
    // 引入 Promise 之后
    sendRequest("test1.html", "").then(function(data1) {
        console.log("第一次请求成功, 这是返回的数据:", data1);
    }).then(function(data2) {
        console.log("第二次请求成功, 这是返回的数据:", data2);
    }).then(function(data3) {
        console.log("第三次请求成功, 这是返回的数据:", data3);
    }).catch(function(error) {
        //用catch捕捉前面的错误
        console.log("sorry, 请求失败了, 这是失败信息:", error);
    });

什么是Promise?
一个 Promise 对象代表一个目前还不可用,但是在未来的某个时间点可以被解析的值。Promise表示一个异步操作的最终结果。

二、Promise/A+基本的规范

一个Promise可能有三种状态:初始状态(pending)、已完成(fulfilled)、已拒绝(rejected)。pending 状态的 Promise 对象可能触发fulfilled 状态并传递一个值给相应的状态处理方法,也可能触发失败状态(rejected)并传递失败信息。当其中任一种情况出现时,Promise 对象的 then 方法绑定的处理方法(handlers )就会被调用(then方法包含两个参数:onfulfilled 和 onrejected,它们都是 Function 类型。当Promise状态为fulfilled时,调用 then 的 onfulfilled 方法,当Promise状态为rejected时,调用 then 的 onrejected 方法, 所以在异步操作的完成和绑定处理方法之间不存在竞争)。

一个Promise的状态只可能从“等待”转到“完成”态或者“拒绝”态,不能逆向转换,同时“完成”态和“拒绝”态不能相互转换。

Promise必须实现then方法(可以说,then就是promise的核心),而且then必须返回一个Promise,同一个Promise的then可以调用多次,并且回调的执行顺序跟它们被定义时的顺序一致。

then方法接受两个参数,第一个参数是成功时的回调,在Promise由“等待”态转换到“完成”态时调用,另一个是失败时的回调,在Promise由“等待”态转换到“拒绝”态时调用。同时,then可以接受另一个Promise传入,也接受一个“类then”的对象或方法,即thenable对象。ajax就是一个thenable对象。

Promise状态变化

优点
有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。

//不友好的层层嵌套
loadImg("a.jpg", function() {  
    loadImg("b.jpg", function() {  
        loadImg("c.jpg", function() {  
            console.log("all done!");  
        });  
    });  
});  

缺点
Promise 也有一些缺点。首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

三、ES6 Promise基本的API 1. Promise.resolve(value) // 生成一个成功的promise对象

返回一个状态由给定value决定的Promise对象。如果该值是一个Promise对象,则直接返回该对象;如果该值是thenable(即,带有then方法的对象),返回的Promise对象的最终状态由then方法执行决定;否则的话(该value为空,基本类型或者不带then方法的对象),返回的Promise对象状态为fulfilled,并且将该value传递给对应的then方法。通常而言,如果你不知道一个值是否是Promise对象,使用Promise.resolve(value) 来返回一个Promise对象,这样就能将该value以Promise对象形式使用。

2. Promise.reject(reason) // 生成错误的一个promise对象

返回一个状态为失败的Promise对象,并将给定的失败信息传递给对应的处理方法

3. Promise.prototype.then() // 核心部分

返回一个新的Promise。

4. Promise.prototype.catch(onRejected) // 异常捕获

添加一个拒绝(rejection) 回调到当前 promise, 返回一个新的promise。当这个回调函数被调用,新 promise 将以它的返回值来resolve,否则如果当前promise 进入fulfilled状态,则以当前promise的完成结果作为新promise的完成结果.

5. Promise.all(iterable)

这个方法返回一个新的promise对象,该promise对象在iterable参数对象里所有的promise对象都成功的时候才会触发成功,一旦有任何一个iterable里面的promise对象失败则立即触发该promise对象的失败。这个新的promise对象在触发成功状态以后,会把一个包含iterable里所有promise返回值的数组作为成功回调的返回值,顺序跟iterable的顺序保持一致;如果这个新的promise对象触发了失败状态,它会把iterable里第一个触发失败的promise对象的错误信息作为它的失败错误信息。Promise.all方法常被用于处理多个promise对象的状态集合。(可以参考jQuery.when方法)

6. Promise.race(iterable) // 最先执行的promise结果

当iterable参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。

如果有一个Promise对象执行完成了,后面的还会不会再继续执行了呢?在ES6 Promises规范中,也没有取消(中断)Promise对象执行的概念,我们必须要确保Promise最终进入resolve or reject状态之一。所以,后面的Promise对象还是会继续执行的。

四、ES6 Promise基本用法 1. 创建Promise对象。

new Promise(fn) 返回一个Promise对象

在 fn 中指定异步等处理。

处理结果正常的话,调用 resolve(处理结果值)。

处理结果错误的话,调用 reject(Error对象)。

//示例
function getURL(URL) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open("GET", URL);
        xhr.onload = () => resolve(xhr.responseText);
        xhr.onerror = () => reject(xhr.statusText);
        xhr.send();
    });
}
// 运行示例
var URL = "http://baidu.com";    
getURL(URL)
.then(function onFulfilled(value){
    console.log(value); 
})
.catch(function onRejected(error){
    console.error(error);
});
// 其实 .catch 只是 Promise.then(undefined, onRejected) 的别名而已,
// 如下代码也可以完 成同样的功能。
getURL(URL).then(onFulfilled, onRejected);
getURL(URL).then(function(value) {
    // fulfillment
    }, function(reason) {
    // rejection
});

总结:
用 new Promise 方法创建promise对象
用 .then 或 .catch 添加promise对象的处理函数

2. Promise.prototype.then()

它的作用是为Promise实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是Resolved状态的回调函数,第二个参数(可选)是Rejected状态的回调函数。

then方法返回的是一个新的Promise实例。因此可以采用链式写法,即then方法后面再调用另一个then方法。

3. Promise.prototype.catch()

Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。

getAjax("url/info").then(function(data) {
    // ...
}).catch(function(error) {
    // 处理 ajax 和 前一个回调函数运行时发生的错误
    console.log("发生错误!", error);
});

总结:
1.上面代码中,getAjax方法返回一个 Promise 对象,如果该对象状态变为Resolved,则会调用then方法指定的回调函数;如果异步操作抛出错误,状态就会变为Rejected,就会调用catch方法指定的回调函数,处理这个错误。另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。
2.Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。

有了then里面的第二个onRejected函数捕获错误,为什么还需要catch?

function throwError(value) { // 抛出异常
    throw new Error(value);
}
// <1> onRejected不会被调用
function main1(onRejected) {
    return Promise.resolve(1).then(throwError, onRejected);
}
// <2> 有异常发生时onRejected会被调用
function main2(onRejected) {
    return Promise.resolve(1).then(throwError).catch(onRejected);
}
// 执行main函数
main1(function(){
    console.log("错误异常");
}
// 执行main2函数
main2(function(){
    console.log("错误异常");
}
/*Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。
一般来说,不要在then方法里面定义Reject状态的回调函数(即then的第二个参数),总是使用catch方法。
*/
Promise.resolve(1).then(throwError).then(null, onRejected);

在函数main1因为虽然我们在的第二个参数中指定了用来错误处理的函数,但实际上它却不能捕获第一个参数指定的函数(本例为throwError)里面出现的错误。
与此相对的是main2中的代码则遵循了 throwError → onRejected 的调用流程。这时候出现异常的话,在会被方法链中的下一个方法,即 .catch 所捕获,进行相应的错误处理。

总结:
.then 方法中的onRejected参数所指定的回调函数,实际上针对的是其Promise对象或者之前的Promise对象,而不是针对方法里面指定的第一个参数,即onFulfilled所指向的对象,这也是then和 catch表现不同的原因。

4. Promise.prototype.finally(onFinally)

添加一个事件处理回调于当前promise对象,并且在原promise对象解析完毕后,返回一个新的promise对象。回调会在当前promise运行完毕后被调用,无论当前promise的状态是完成(fulfilled)还是失败(rejected)

5. Promise.resolve()

有时需要将现有对象转为Promise对象,Promise.resolve方法就起到这个作用。
该函数的参数四种情况:
(1)参数是一个Promise实例,那么Promise.resolve将不做任何操作,原封不动的将实例返回。
(2)参数是一个thenable对象,会将其转为Promise对象,然后立即执行该对象的then方法。
(3)参数不是具有then方法的对象,或根本就不是对象。比如说字符之类,则Promise.resolve方法返回一个新的Promise对象,并且状态Resolved。
(4)不带有任何参数,直接返回一个状态为Resolved的Promise对象。

使用Promise.resolve()创建Promise对象

// 静态方法 Promise.resolve(value) 可以认为是 new Promise() 方法的快捷方式。
// 比如 
Promise.resolve(1)
.then(function(value){
    console.log(value);
});  
// 可以认为是以下代码的语法糖。
new Promise(function(resolve){ 
    resolve(1);
})
.then(function(value){
    console.log(value);
});
// 控制台输出1
注意:无论Promise.resolve的参数是什么,只要变成了rejected或者resolved。都会执行then里面的resolve函数。

resolve另一个promise对象

var original = Promise.resolve("我在第二行");
var cast = Promise.resolve(original);
cast.then(function(value) {
    console.log("value: " + value);
});
console.log("original === cast ? " + (original === cast));

/*
* 打印顺序如下,这里有一个同步异步先后执行的区别
* original === cast ? true
* value: 我在第二行
*/

将 thenable 对象转换为promise对象。 什么是thenable对象?
简单来说它就是一个非常类似promise的东西。thenable指的是一个
具有 .then 方法的对象。jQuery.ajax(),这个对象具有 .then 方法。

// Resolve一个thenable对象
var p1 = Promise.resolve({ 
    then: function(onFulfill, onReject) { onFulfill("fulfilled!"); }
});
console.log(p1 instanceof Promise) // true, 这是一个Promise对象

p1.then(function(v) {
      console.log(v); // 输出"fulfilled!"
    }, function(e) {
      // 不会被调用
});

// Thenable在callback之前抛出异常
// Promise rejects
var thenable = { then: function(resolve) {
    throw new TypeError("Throwing");
    resolve("Resolving");
}};

var p2 = Promise.resolve(thenable);
p2.then(function(v) {
    // 不会被调用
}, function(e) {
    console.log(e); // TypeError: Throwing
});

// Thenable在callback之后抛出异常
// Promise resolves
var thenable = { then: function(resolve) {
    resolve("Resolving");
    throw new TypeError("Throwing");
}};

var p3 = Promise.resolve(thenable);
p3.then(function(v) {
    console.log(v); // 输出"Resolving"
}, function(e) {
    // 不会被调用
});

6. Promise.reject()

Promise.reject(reason)方法也会返回一个新的Promise实例,该实例的状态为rejected。

Promise.reject("Testing static reject").then(function(reason) {
    // 未被调用
}, function(reason) {
    console.log(reason); // "Testing static reject"
});

Promise.reject(new Error("fail")).then(function(result) {
    // 未被调用
}, function(error) {
    console.log(error); // stacktrace
});

// 注意:无论Promise.reject的参数是什么,只要变成了rejected或者resolved。都会执行then里面的reject函数。
7. Promise.all(iterable)

Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果。

var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise((resolve, reject) => {
    setTimeout(resolve, 100, "foo");
}); 

Promise.all([p1, p2, p3]).then(values => { 
    console.log(values); // [3, 1337, "foo"] 
});

Promise.race(iterable)

race 函数返回一个 Promise,它将与第一个传递的 promise 相同的完成方式被完成。它可以是完成( resolves),也可以是失败(rejects),这要取决于第一个完成的方式是两个中的哪个。
如果传的迭代是空的,则返回的 promise 将永远等待。
如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,则 Promise.race 将解析为迭代中找到的第一个值。

var p1 = new Promise(function(resolve, reject) { 
    setTimeout(resolve, 500, "one"); 
});
var p2 = new Promise(function(resolve, reject) { 
    setTimeout(resolve, 100, "two"); 
});

Promise.race([p1, p2]).then(function(value) {
  console.log(value); // "two"
  // 两个都完成,但 p2 更快
});

var p3 = new Promise(function(resolve, reject) { 
    setTimeout(resolve, 100, "three");
});
var p4 = new Promise(function(resolve, reject) { 
    setTimeout(reject, 500, "four"); 
});

Promise.race([p3, p4]).then(function(value) {
  console.log(value); // "three"
  // p3 更快,所以它完成了              
}, function(reason) {
  // 未被调用
});

var p5 = new Promise(function(resolve, reject) { 
    setTimeout(resolve, 500, "five"); 
});
var p6 = new Promise(function(resolve, reject) { 
    setTimeout(reject, 100, "six");
});

Promise.race([p5, p6]).then(function(value) {
  // 未被调用             
}, function(reason) {
  console.log(reason); // "six"
  // p6 更快,所以它失败了
});
五、Promise只能进行异步操作?
Promise在规范上规定Promise只能使用异步调用方式。
```
// 可以看出promise是 一个异步函数  
var promise = new Promise(function(resolve) {
    console.log("inner promise");                // 1 
    resolve(42);
});
promise.then(function(value) {
    console.log(value);                          // 3 
});
console.log("outer promise");                    // 2
```
why?

因为同步调用和异步调用同时存在容易导致一些混乱。举个类似的例子。

function onReady(fn) {
    var readyState = document.readyState;
    if (readyState === "interactive" || readyState === "complete") {  // 已加载,文档与用户可以开始交互 || 载入完成
        fn();
    } else {
        window.addEventListener("DOMContentLoaded", fn); 
    }
}
onReady(function () {
    console.log("DOM fully loaded and parsed");
});
console.log("==Starting==");

如上js函数会根据执行时DOM是否已经装载完毕来决定是对回调函数进行同步调用还是异步调用。因此,如果这段代码在源文件中出现的位置不同,在控制台上打印的log消息顺序也会不同。为了解决这个问题,我们可以选择统一使用异步调用的方式。

function onReadyPromise() {
    return new Promise(function (resolve, reject) {
        var readyState = document.readyState;
        if (readyState === "interactive" || readyState === "complete") {   // 已加载,文档与用户可以开始交互 || 载入完成
            resolve(); 
        } else {
            window.addEventListener("DOMContentLoaded", resolve); 
        }
    }); 
}
onReadyPromise().then(function () { 
    console.log("DOM fully loaded and parsed");
}); 
console.log("==Starting==");

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/102416.html

相关文章

  • JS-Promise

    摘要:对象表示异步操作的最终完成或失败及其结果值。状态初始状态,未完成或拒绝。返回使用给定值解析的对象。根据的属性选择返回对应的状态简简单单的叙述下常用的几个属性,有不对的地方请指教昨天看了一篇文章,还是挺有启发的。。。。。 Promise The Promise object represents the eventual completion (or failure) of an asy...

    widuu 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<