02月27, 2018

Promise 对象使用

Promise 介绍

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

const executor = function(resolve, reject) {
    /* code */ 
} 
new Promise(executor)

  executor是带有resolve和reject两个参数的函数。Promise构造函数执行时立即调用executor函数,resolve和reject两个函数作为参数传递给executor(executor函数在Promise构造函数返回新建对象前被调用)。resolve和reject函数被调用时,分别将promise的状态改为fulfilled(完成)或rejected(失败)。executor内部通常会执行一些异步操作,一旦完成,可以调用resolve函数来将promise状态改成fulfilled,或者在发生错误时将它的状态改为rejected。

  如果在executor函数中抛出一个错误,那么该promise状态为rejected。executor函数的返回值被忽略。

  Promise对象是一个代理对象(代理一个值),被代理的值在Promise对象创建时可能是未知的。它允许你为异步操作的成功和失败分别绑定相应的处理方法(handlers)。这让异步方法可以像同步方法那样返回值,但并不是立即返回最终执行结果,而是一个能代表未来出现的结果的promise对象。

一个 Promise有以下几种状态:

  • pending: 初始状态,既不是成功,也不是失败状态。
  • fulfilled: 意味着操作成功完成。
  • rejected: 意味着操作失败。

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

单个 Promise 简单使用

用法简介

const promise = new Promise(function (resolve, reject) {
    let flag = false;
    //这里写需要异步的代码
    // async code


    //执行成功时,调用 resolve
    if (flag) {
        resolve();//resolve可以放置异步函数的返回值,也可是一个Promise对象

    }else{ //执行失败时,调用 reject

        reject(new Error("执行出错"))
    }
});

promise.then(function (result) { //调用 resolve 时会回调这个函数,并把参数传给它
    // 异步成功之后需要执行的代码
},function (result) { //调用 reject 时会回调这个函数,并把参数传给它
    // 异步失败之后需要执行的代码
});

用法示例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="application/javascript">
        cconst async = function (reject, reject, url) {
            const handler = function () {
                if (this.readyState !== 4) {
                    return;
                }
                if (this.status === 200) {
                    reject(this.response);
                } else {
                    reject(new Error(this.statusText));
                }
            };
            const client = new XMLHttpRequest();
            client.open("GET", url);
            client.onreadystatechange = handler;
            client.responseType = "text";
            client.setRequestHeader("Accept", "application/text");
            client.send();
        }


        const getJSON = function (url) {
            const promise = new Promise(function (resolve, reject) {
                async(reject, reject, url);
            });

            return promise;
        };

        function ajax() {
            getJSON("http://www.bootcdn.cn/jquery/").then(function (json) {
                let div = window.document.createElement("div");
                div.html(json)
                window.document.lastChild.appendChild(div)
            }, function (error) {
                window.document.write(error)
            });
        }
    </script>
</head>
<body>
<button onclick="ajax()">模拟Ajax</button>
</body>
</html>

多个 Promise 嵌套使用

Promise 构造函数中的 resolve 参数可以是一个 Promise 对象,此时这个 Promise 对象中的方法会优先于 本 Promise 对象中的方法执行。

//定义一个p1
const p1 = new Promise(function (resolve, reject) {
    console.log("p1 开始休眠")
    setTimeout(function () {
        console.log("p1 休眠结束")
        resolve('hello')
    }, 1000)
})

const p2 = new Promise(function (resolve, reject) {
    console.log("p2 开始休眠")
    setTimeout(function () {
        console.log("p2 休眠结束")
        resolve(p1)
    }, 1000)
})


p2.then(function (result) {
    console.log(`${result} world`)
})

/* 打印结果
p1 开始休眠
p2 开始休眠
p2 休眠结束
p1 休眠结束
hello world
*/

//也可以直接使用链式调用
const p1 = new Promise(function (resolve, reject) {
    console.log("p1 开始休眠")
    setTimeout(function () {
        console.log("p1 休眠结束")
        resolve("hello")
    }, 1000)
})

p1.then(function (value) {
    console.log(`第一步 ${value}`)
    return `${value} world`;
}).then(function (value) {
    console.log(`第二步 ${value}`)
})

/**
 * 打印结果
 * p1 开始休眠
 * p1 休眠结束
 * 第一步 hello
 * 第二步 hello world
 */

Promise.prototype 原型

属性

  • Promise.prototype.constructor 返回被创建的实例函数. 默认为 Promise 函数.

方法

  • Promise.prototype.catch(onRejected)

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

  • Promise.prototype.then(onFulfilled, onRejected)

    添加解决(fulfillment)和拒绝(rejection)回调到当前promise,返回一个新的 promise, 将以回调的返回值来resolve.

  • Promise.prototype.finally(onFinally)

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

Promise 静态方法

Promise.all(iterable)

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

  这个方法有点类似与多线程编程中的Master-Worker模式,每一个promise对象p1,p2,p3都执行自己的异步代码,最后把所有的结果汇总到Promise.all()返回的对象中,在其返回的promise对象中处理最终结果。但是要注意如果其中的代码不是异步的,最后js代码还是需要放到主线程中来顺序执行。

let start = Date.now();
const getPromise = function () {
    return new Promise(function (success, failure) {
        for (let i = 0; i < 200000000; i++) {
        }
        success(100)
    });
};
const  p1 = getPromise();
const  p2 = getPromise();
const  p3 = getPromise();

let arr = [p1,p2,p3];
let promise = Promise.all(arr);

promise.then(function (values) {
    console.log(values)
});


//Promise的代码还是在主线程中执行,then的代码却是异步
let start = Date.now()
const getPromise = function (name) {
    return new Promise(function (success, failure) {
        for (let i = 0; i < 1000000000; i++) {
        }
        success(name);
        console.log(`${name} ${Date.now() - start}`)
    });
};
const  p1 = getPromise("p1");
const  p2 = getPromise("p2");
const  p3 = getPromise("p3");

p1.then(function (name) {
    console.log(`${name} 执行完毕 ${Date.now()}`)
})
p2.then(function (name) {
    console.log(`${name} 执行完毕 ${Date.now()}`)
})
p3.then(function (name) {
    console.log(`${name} 执行完毕 ${Date.now()}`)
})

let arr = [p1,p2,p3];
let promise = Promise.all(arr);
promise.then(function (results) {
    console.log(` 执行结果 ${results} 是否为数组 ${results instanceof Array} ${Date.now()}`)  
});

console.log("=============")

/** 打印结果  
    p1 1727
    p2 3127
    p3 4687
    =============
    p1 执行完毕 1519712458799
    p2 执行完毕 1519712458799
    p3 执行完毕 1519712458799
    执行结果 p1,p2,p3 是否为数组 true 1519712692907
*/

从打印结果看,Promise构造函数中的代码(executor)是同步阻塞的,而promise.then方法中的代码却是异步的,且几乎是在所有的executor中代码执行完毕,才触发执行的。

Promise.race(iterable)

  该方法返回一个Promise对象promise,当iterable里第一个执行完毕后(例子中的p1)就会触发promisethen方法,then的执行方式由p1执行结果决定。

let start = Date.now()
const getPromise = function (name,time) {
    return new Promise(function (success, failure) {
        setTimeout(function () {
            if(name === 'p3'){
                failure(name);
            }else {
                success(name)
            }
        },time)
    });
};
const  p1 = getPromise("p1",150);
const  p2 = getPromise("p2",200);
const  p3 = getPromise("p3",300);
p1.then(function (name) {
    console.log(`${name} 执行完毕 ${Date.now()}`)
})
p2.then(function (name) {
    console.log(`${name} 执行完毕 ${Date.now()}`)
})
p3.then(function(){},function (name) {
    console.log(`${name} 执行失败 ${Date.now()}`)
})
let arr = [p1,p2,p3];
let promise = Promise.race(arr);
promise.then(function (results) {
    console.log(`race的值: ${results} success ${Date.now()}`)
},function (results) {
    console.log(`race的值: ${results} failure ${typeof results} ${Date.now()}`)
});

/** 打印结果
p1 执行完毕 1522650576957
race的值: p1 success 1522650576962
p2 执行完毕 1522650577006
p3 执行失败 1522650577106
 */

我们可以通过更换p1,p2,p3的等待时间来查看promise对象中的值,这样可以更容易让我们明白race是执行方式。

Promise.reject(reason)

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

const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))

p.then(null, function (s) {
  console.log(s)
});

上面代码生成一个Promise对象的实例p,状态为rejected,回调函数会立即执行。注意,Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。这一点与Promise.resolve方法不一致。

const thenable = {
  then(resolve, reject) {
    reject('出错了');
  }
};

Promise.reject(thenable)
.catch(e => {
  console.log(e === thenable)
})
// true

上面代码中,Promise.reject方法的参数是一个thenable对象,执行以后,后面catch方法的参数不是reject抛出的“出错了”这个字符串,而是thenable对象。

Promise.resolve(value)

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

Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
  • (1)参数是一个 Promise 实例 如果参数是Promise实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
const p1 = new Promise(function (s,f) {
});
let promise = Promise.resolve(p1);
console.log(p1 === promise) //true
  • (2)参数是一个thenable对象 Promise.resolve方法会将这个对象转为Promise对象,然后就立即执行thenable对象的then方法。thenable对象的then方法执行后,对象p1的状态就变为resolved,从而立即执行最后那个then方法指定的回调函数,输出 42。
let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};

let p1 = Promise.resolve(thenable);
p1.then(function(value) {
  console.log(value);  // 42
});
};
  • (3)参数不是具有then方法的对象,或根本就不是对象 如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。
const p = Promise.resolve('Hello');

p.then(function (s){
  console.log(s)
});
// Hello

上面代码生成一个新的Promise对象的实例p。由于字符串Hello不属于异步操作(判断方法是字符串对象不具有then方法),返回Promise实例的状态从一生成就是resolved,所以回调函数会立即执行。Promise.resolve方法的参数,会同时传给回调函数。

  • (4)不带有任何参数 Promise.resolve方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。
const p = Promise.resolve();
//等同于
p.then(function () {
  // ...
});

参考

阮一峰ECMAScript 6 入门

官方文档

本文链接:https://www.qiangshuidiyu.xin/post/promise.html

-- EOF --

Comments