博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ES6 Promise - 让我们解开的面纱(遵循Promise/A+规范)
阅读量:5980 次
发布时间:2019-06-20

本文共 16769 字,大约阅读时间需要 55 分钟。

Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息。ECMAscript 6 原生提供了 Promise 对象。而且ES7中的async/await也是Promise基础实现的。Promise到底有魅力和作用呢?本文将解开它的面纱,探索promise的原理及用法。不用再被一些回调地狱、各种异步调用所头疼烦恼。

什么是Promise?

     promise,是承诺的意思。在JavaScript中promise指一个的对象或函数(是一个包含了兼容promise规范then方法的对象或函数)。  Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。
  1. promise的特点

1-1、 Promise的三种状态   

  • pending: Promise对象实例创建时候的初始状态
  • resolved:可以理解为成功的状态
  • rejected:可以理解为失败的状态      
  1-2、 根据Promise/A+规范,可知
  • 如果是pending状态,则promise:可以转换到resolved或rejected状态。
  • 如果是resolved状态,则promise:不能转换成任何其它状态。必须有一个值,且这个值不能被改变。
  • 如果是rejected状态,则promise可以:不能转换成任何其它状态。必须有一个原因,且这个值不能被改变。
”值不能被改变”指的是其identity不能被改变,而不是指其成员内容不能被改变。

1-3、promise 有一个 then 方法,就是用来指定Promise 对象的状态改变时确定执行的操作,resolve 时执行第一个函数 (onFulfilled),reject 时执行第二个函数(onRejected)

promiseFn().then(resolve(onFulfilled){        //当promise状态变成fulfilled时,调用此函数    },reject(onRejected){        //当promise状态变成rejected时,调用此函数    });复制代码
  •  resolve,reject 都是可选参数
  1.  如果onFulfilled不是一个函数,则忽略之。
  2. 如果onRejected不是一个函数,则忽略之。
  • 如果onFulfilled是一个函数:
    1. 它必须在promise fulfilled后调用, 且promise的value为其第一个参数。
    2. 它不能在promise fulfilled前调用。
    3. 不能被多次调用。
  • 如果onRejected是一个函数,
    1. 它必须在promise rejected后调用, 且promise的reason为其第一个参数。
    2. 它不能在promise rejected前调用。
    3. 不能被多次调用。
  • onFulfilledonRejected 只允许在 栈仅包含平台代码时运行. 
  • onFulfilledonRejected 必须被当做函数调用 (i.e. 即函数体内的 thisundefined). 
  • 对于一个promise,它的then方法可以调用多次.
    1. promise fulfilled后,所有onFulfilled都必须按照其注册顺序执行。
    2. promise rejected后,所有OnRejected都必须按照其注册顺序执行。
  • then 必须返回一个promise .

    promise2 = promise1.then(onFulfilled, onRejected);复制代码
    1. 如果onFulfilledonRejected 返回了值x, 则执行Promise 解析流程[[Resolve]](promise2, x).
    2. 如果onFulfilledonRejected抛出了异常e, 则promise2应当以ereason被拒绝。
    3. 如果 onFulfilled 不是一个函数且promise1已经fulfilled,则promise2必须以promise1的值fulfilled.
    4. 如果 OnReject 不是一个函数且promise1已经rejected, 则promise2必须以相同的reason被拒绝.

 

2、为什么使用promise?有什么好处呢?

  • 2-1 对于回调函数:  可以解决回调地狱,如下图:         例如,使用jQuery的ajax多次向后台请求数据时,并且每个请求之间需要相互依赖,则需要回调函数嵌套来解决而形成“回调地狱”。

    $.get(url1, data1 => {    console.log(data1,"第一次请求");    $.get(data1.url, data2 => { // 第一次请求后的返回url 在此请求后台        console.log(data2,"第二次请求")        .....    })})复制代码
这样一来,在处理越多的异步逻辑时,就需要越深的回调嵌套,复制代码

这种编码模式的问题主要有以下几个: 

  1. 代码逻辑书写顺序与执行顺序不一致,不利于阅读与维护。
  2.  异步操作的顺序变更时,需要大规模的代码重构。
  3.  回调函数基本都是匿名函数,bug 追踪困难。 
  4. 回调函数是被第三方库代码(如上例中的 ajax )而非自己的业务代码所调用的,造成了 IoC 控制反转。
  5. 结果不能通过return返回

Promise 怎么解决呢?

let p = url1 => {     return new Promise((resolve, reject) => {        $.get(url, data => {            resolve(data)        });    })};// p(url).then(resvloe => {    return p(resvloe.url);   }).then(resvloe2 => {    return resvloe2(resvloe2.url);}).then(resvloe3 => {    console.log(resvloe3);}).catch(err => throw new Error(err));当第一个then中返回一个promise,会将返回的promise的结果,传递到下一个then中。这就是比较著名的链式调用了。   复制代码
  • 2-2、Promise 也有一些缺点。
    首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

3、Promise 使用

  • 1. 创建Promise。要想创建一个 promise 对象、可以使用 new 来调用 Promise 的构造器来进行实例化。接收一个excutor执行函数作为参数, excutor有两个函数类型形参resolve reject。

let promise = new Promise(function(resolve, reject) {    // 异步处理    // 处理结束后、调用resolve 或 reject});复制代码

  • 2.promise中的状态变化。
1. promise 对象初始化状态为 pending 
2. 当调用resolve(成功),会由pending => resolved 
3. 当调用reject(失败),会由pending => rejected
  • 3. promise对象方法 then方法
3-1.then方法  注册:对于已经实例化过的 promise 对象可以调用 promise.then() 方法,传递 resolve 和 reject 方法作为回调。promise.then() 是 promise 最为常用的方法。

// onFulfilled 是用来接收promise成功的值  // onRejected 是用来接收promise失败的原因 // then方法是异步的promise.then(onFulfilled, onRejected);复制代码

3-2. resolve(成功): onFulfilled会被调用

let promise = new Promise((resolve, reject) => {   resolve('fulfilled'); // 状态由 pending => fulfilled});promise.then(result => { // onFulfilled    console.log(result); // 'fulfilled' }, reason => { // onRejected 不会被调用    })复制代码

3-3.reject(失败) onRejected会被调用

let promise = new Promise((resolve, reject) => {   reject('rejected'); // 状态由 pending => rejected});promise.then(result => { // onFulfilled 不会被调用  }, reason => { // onRejected     console.log(reason); // 'rejected'})复制代码

3-4.promise.catch 方法:捕捉错误 

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

promise.catch(onRejected)相当于promise.then(null, onRrejected);// 注意// onRejected 不能捕获当前onFulfilled中的异常promise.then(onFulfilled, onRrejected); // 可以写成:promise.then(onFulfilled)       .catch(onRrejected);   复制代码

3-5、promise chain  方法每次调用 都返回一个新的promise对象 所以可以链式写法
Promise的静态方法

function step1() {    console.log("step1");}function step2() {    console.log("step2");}function onRejected(error) {    console.log("错误方法:", error);}var promise = Promise.resolve();promise    .then(step1)    .then(step2)    .catch(onRejected) // 捕获前面then方法中的异常复制代码

4、Promise的方法是使用

  1. Promise.resolve 返回一个fulfilled状态的promise对象
Promise.resolve('成功');// 相当于let promise = new Promise(resolve => {   resolve('成功');});复制代码

//库中实现Promise.resolve = function (val) {  return new Promise((resolve, reject) => resolve(val))}复制代码

2.Promise.reject 返回一个rejected状态的promise对象

var p = Promise.reject('出错了'); p.then(null, function (s){  console.log(s)});// 出错了复制代码

3.Promise.all 接收一个promise对象数组为参数

只有全部为resolve才会调用 通常会用来处理 多个并行异步操作

const p1 = new Promise((resolve, reject) => {    resolve(1);});const p2 = new Promise((resolve, reject) => {    resolve(2);});const p3 = new Promise((resolve, reject) => {    reject(3);});Promise.all([p1, p2, p3]).then(data => {     console.log(data); // [1, 2, 3] 结果顺序和promise实例数组顺序是一致的}, err => {    console.log(err);});复制代码

//库中实现Promise.all = function(arr) {     return new Promise((resolve, reject) => {        let num = 0,innerArr = [];       function done(index,data){            innerArr[index] = data;            num ++;            if(num === arr.length){                resolve(innerArr);            }                   }      for(let i =0 ;i
{ done(i,res); },reject); // 有一个失败 就返回 } })}复制代码

4.Promise.race 接收一个promise对象数组为参数

Promise.race 只要有一个promise对象进入 FulFilled 或者 Rejected 状态的话,就会继续进行后面的处理。

function timerPromisefy(delay) {    return new Promise(function (resolve, reject) {        setTimeout(function () {            resolve(delay);        }, delay);    });}var startDate = Date.now();Promise.race([    timerPromisefy(10),    timerPromisefy(20),    timerPromisefy(30)]).then(function (values) {    console.log(values); // 10});复制代码

// 库中实现Promise.race = function(arr) {    return new Promise((resolve, reject) => {        arr.forEach((item, index) => {            item.then(resolve, reject);        });    });}复制代码

5.Promise的finally

Promise.finally = function (callback) {  let P = this.constructor;  return this.then(    value  => P.resolve(callback()).then(() => value),    reason => P.resolve(callback()).then(() => { throw reason })  );};复制代码

5、prmoise 代码库实现

/** * Promise 实现 遵循promise/A+规范 * Promise/A+规范译文: * https://promisesaplus.com/ */// 判断x是不是promise 根据规范function resolvePromise(promise2, x, resolve, reject) {    // 1、如果promise2 和 x 指向相同的值, 使用 TypeError做为原因将promise拒绝。 (就会导致循环引用报错)    if (promise2 === x) {        return reject(new TypeError('循环引用'));    }    // 避免多次调用    let isUsed = false;    // 2、如果x是一个promise对象 (该判断和下面 判断是不是thenable(thenable 是一个包含了then方法的对象或函数)对象重复 所以可有可无)     /**     * 如果x是pending状态,promise必须保持pending走到x resolved或rejected.        如果x是resolved状态,将x的值用于resolve promise.        如果x是rejected状态, 将x的原因用于reject promise..     */    // if (x instanceof Promise) { // 获得它的终值 继续resolve    //     if (x.status === 'pending') { // 如果为等待态需等待直至 x 被执行或拒绝 并解析y值 递归    //         x.then(y => {    //             resolvePromise(promise2, y, resolve, reject);    //         }, reason => {    //             reject(reason);    //         });    //     } else { // 如果 x 已经处于执行态/拒绝态(值已经被解析为普通值),用相同的值执行传递下去 promise    //         x.then(resolve, reject);    //     }    //     // 3、如果 x 为对象或者函数    //     /**    //      *  1.将 then 赋为 x.then.     //         2.如果在取x.then值时抛出了异常,则以这个异常做为原因将promise拒绝。    //         3.如果 then 是一个函数, 以x为this调用then函数, 且第一个参数是resolve,第二个参数是reject,且:    //            3-1.当 resolve 被以 y为参数调用, 执行 [[Resolve]](promise, y).    //            3-2.当 reject 被以 reason 为参数调用, 则以reason为原因将promise拒绝。    //            3-3.如果 resolvee 和 reject 都被调用了,或者被调用了多次,则只第一次有效,后面的忽略。    //            3-4.如果在调用then时抛出了异常,则:    //                 如果 resolve 或 reject 已经被调用了,则忽略它。isUsed = true;    //                 否则, 以e为reason将 promise 拒绝。    //         4.如果 then不是一个函数,则 以x为值resolve promise。    //      */    // } else     if (x !== null && ((typeof x === 'object') || (typeof x === 'function'))) {        try { // 是否是thenable对象(具有then方法的对象/函数)  如果在取x.then值时抛出了异常,则以这个异常做为原因将promise拒绝。            let then = x.then;            if (typeof then === 'function') {                then.call(x, y => {  // 如果y是promise就继续递归解析promise                    if (isUsed) return;                    isUsed = true;                    resolvePromise(promise2, y, resolve, reject);                }, reason => {  // 只要失败了就失败了 不用再递归解析是都是promise                    if (isUsed) return;                    isUsed = true;                    reject(reason);                })            } else { // 说明是一个函数,则 以x为值resolve promise。                resolve(x);            }        } catch (e) {            if (isUsed) return;            isUsed = true;            reject(e);        }    } else {  //4、如果 x 不是对象也不是函数,则以x为值 resolve promise。例如 x = 123 或 x ='成功'        resolve(x);    }}// Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息。// Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。class Promise {    // Promise 是一个类, new Promise 返回一个 promise对象 接收一个ex执行函数作为参数, ex有两个函数类型形参resolve reject    /**     * var promise = new Promise(function(resolve, reject) { //会立即执行        // 异步处理        // 处理结束后、调用resolve 或 reject   //当异步代码执行成功时,我们才会调用resolve(...), 当异步代码失败时就会调用reject(...)        //在本例中,我们使用setTimeout(...)来模拟异步代码,实际编码时可能是XHR请求或是HTML5的一些API方法.           setTimeout(function(){      resolve("成功!"); //代码正常执行!        }, 250);      });      promise.then(function(successMessage){            //successMessage的值是上面调用resolve(...)方法传入的值.            //successMessage参数不一定非要是字符串类型,这里只是举个例子            console.log("Yay! " + successMessage);        });          */    constructor(ex) {  //   this.status = 'pending'; // 初始状态 (表示 未开始) this.resolveVal = undefined;  // resolved状态时(表示成功) 返回的信息 this.rejectVal = undefined;  // rejected状态时(表示失败) 返回的信息 this.onResolveCallBackFns = [];   // 存储resolved状态对应的onResolved函数 (因为可以链式调用可以多个then方法)        this.onRejectCallBackFns = [];    // 存储rejected状态对应的onRejected函数        /**    * 一个Promise必须处在其中之一的状态:pending, fulfilled 或 rejected.         *          * 如果是pending状态,则promise:            可以转换到resolved或rejected状态。            如果是resolved状态,则promise:            不能转换成任何其它状态。            必须有一个值,且这个值不能被改变。            如果是rejected状态,则promise可以:            不能转换成任何其它状态。            必须有一个原因,且这个值不能被改变。            ”值不能被改变”指的是其identity不能被改变,而不是指其成员内容不能被改变。        */        let resolve = (data) => {   // data 成功态时接收的终值            if (this.status === 'pending') {  // 只能由pedning状态 => resolved状态 避免调用多次resolve reject)                this.status = 'resolved';                this.resolveVal = data;                this.onResolveCallBackFns.forEach(cb => cb());            }        }        let reject = (err) => {            if (this.status === 'pending') {  // 只能由pedning状态 => rejected状态 避免调用多次resolve reject)                this.status = 'rejected';                this.rejectVal = err;                this.onRejectCallBackFns.forEach(cb => cb());            }        }        // 捕获在ex执行器中抛出的异常        // new Promise((resolve, reject) => {        //     throw new Error('error in ex')        // })        try {            ex(resolve, reject);        } catch (e) {            reject(e);        }    }    //  按照prmoise a+ 规范  then方法接受两个参数  then方法是异步执行的  必须返回一个promise    then(resolve, reject) {        //  then 方法 resolve,reject 都是可选参数 保证参数后续能够继续执行        //       1.1、  如果resolve不是一个函数,则忽略之。 如果reject不是一个函数,则忽略之。        resolve = typeof resolve == 'function' ? resolve : y => y;            reject = typeof reject == 'function' ? reject : err => { throw err };        let promise2; // then必须返回一个promise        if (this.status === 'pending') { // 等待态            // 当异步调用resolve/rejected时 将resolve/reject收集暂存到集合中                      promise2 = new Promise((res, rej) => {                this.onResolveCallBackFns.push(() => {                    setTimeout(() => {                        try {                            // resolvePromise可以解析x和promise2之间的关系                             /** 如果resolve 或 reject 返回了值x, 则执行Promise 解析流程[[Resolve]](promise2, x).                            // 如果resolve 或 reject 抛出了异常e, 则promise2应当以e为rejectVal被拒绝。                            // 如果 resolve 不是一个函数且promise1已经resolved,则promise2必须以promise1的值resolved.                            // 如果 reject 不是一个函数且promise1已经rejected, 则promise2必须以相同的rejectVal被拒绝.                            */                            let x = resolve(this.resolveVal);                            resolvePromise(promise2, x, res, rej);                        } catch (e) {                            rej(e);                        }                    }, 0);                                   });                this.onRejectCallBackFns.push(() => {                    setTimeout(() => {                        try {                            let x = reject(this.rejectVal);                            resolvePromise(promise2, x, res, rej);                        } catch (e) {                            rej(e);                        }                      }, 0);                      });            });        }        if (this.status == 'resolved') {   // 它必须在promise resolved后调用, 且promise的value为其第一个参数。            promise2 = new Promise((res, rej) => {                //  用setTimeout方法原因 :                 //  1、方法是异步的                 // 2、  对于一个promise,它的then方法可以调用多次.(当在其他程序中多次调用同一个promise的then时 由于之前状态已经为resolved/rejected状态,则会走的下面逻辑),                // 所以要确保为resolved/rejected状态后 也要异步执行resolve/reject 保持统一                setTimeout(() => {                    try {                       let x = resolve(this.resolveVal);                        resolvePromise(promise2, x, res, rej); //  // resolvePromise可以解析x和promise2之间的关系                     } catch (e) {                        rej(e);                    }                })            })        }        if (this.status == 'rejected') {  // 必须在promise rejected后调用, 且promise的rejectVal为其第一个参数            promise2 = new Promise((res, rej) => {                //    方法是异步的  所以用setTimeout方法                 setTimeout(() => {                    try {                        let x = reject(this.rejectVal);                        resolvePromise(promise2, x, res, rej);   // resolvePromise可以解析x和promise2之间的关系                     } catch (e) {                        rej(e);                    }                });            })        }        return promise2; // 调用then后返回一个新的promise    }    // catch接收的参数 只用错误 catch就是then的没有成功的简写    catch(err) {        return this.then(null, err);    }}/** * Promise.all Promise进行并行处理 * 参数: arr对象组成的数组作为参数 * 返回值: 返回一个Promise实例 * 当这个数组里的所有promise对象全部变为resolve状态的时候,才会resolve。 */Promise.all = function (arr) {    return new Promise((resolve, reject) => {        let num = 0, innerArr = [];        function done(index, data) {            innerArr[index] = data;            num++;            if (num === arr.length) {                resolve(innerArr);            }        }        for (let i = 0; i < arr.length; i++) {            arr[i].then((res) => {                done(i, res);            }, reject); // 有一个失败 就返回        }    })}/** * Promise.race * 参数: 接收 promise对象组成的数组作为参数 * 返回值: 返回一个Promise实例 * 只要有一个promise对象进入 resolved 或者 rejected 状态的话,就会继续进行后面的处理(取决于哪一个更快) */Promise.race = function (arr) {    return new Promise((resolve, reject) => {        arr.forEach((item, index) => {            item.then(resolve, reject);        });    });}// Promise.reject 返回一个rejected状态的promise对象Promise.resolve = function (val) {    return new Promise((resolve, reject) => resolve(val))}// .Promise.resolve 返回一个fulfilled状态的promise对象Promise.reject = function (val) {    return new Promise((resolve, reject) => reject(val));}Promise.deferred = Promise.defer = function () {    let dfd = {};    dfd.promise = new Promise((resolve, reject) => {        dfd.resolve = resolve;        dfd.reject = reject;    })    return dfd;}module.exports = Promise;复制代码

6、测试

let p = new Promise((resolve, reject) => {  reject('err');})p.then().then().catch(r => {  console.log(r);}).then(data => {  console.log('data', data);})执行结果:errdata undefined复制代码

let fs = require('fs');function read() {  // 好处就是解决嵌套问题  // 坏处错误处理不方便了  let defer = Promise.defer();  fs.readFile('./1.txt', 'utf8', (err, data) => {    if (err) defer.reject(err);    defer.resolve(data)  });  return defer.promise;}read().then(data => {  console.log(data);});执行结果我是1.txt内容复制代码

转载地址:http://orlox.baihongyu.com/

你可能感兴趣的文章
剑指offer 面试题6:重建二叉树
查看>>
智能合约从入门到精通:Solidity语法之内存变量的布局和状态变量的存储模型...
查看>>
基于ES5`defineProperty` 实现简单的 Mvvm框架
查看>>
关于UI设计的一些工作了解
查看>>
spring cloud构建互联网分布式微服务云平台-Spring Cloud Config环境库
查看>>
java B2B2C Springcloud仿淘宝电子商城系统-Zipkin服务端配置
查看>>
Node.js的npm全局安装包引用
查看>>
js事件杂谈
查看>>
SQL Server基础知识 -- SQL 用于各种数据库的数据类型
查看>>
angularjs~ng-class
查看>>
我的友情链接
查看>>
Linux—文件系统挂载与自动挂载
查看>>
Win 2008 R2安装SQL Server 2008“性能计数器注册表配置单元一致性”失败的解决办法...
查看>>
ROS标记时需要注意的
查看>>
Android获取天气预报
查看>>
django之防止csrf跨站***
查看>>
applicationcontext.xml怎么分模块简化配置
查看>>
Django Admin
查看>>
负载均衡 (一) 工作模式以及工作原理
查看>>
Android 70道面试题汇总不再愁面试
查看>>