你了解Promise吗?(这多是讲述promise最完整的专栏)

导读

在异步编程中,Promise扮演了举足轻重的角色,它解决了ajax请求过程当中的回调地狱的问题,令代码更具可读性。下面的介绍中,笔者会经过一些片断代码,加上一些其本身的理解带你们一块儿从新温故一下Promise为编程所带来的便利。javascript

Promise是抽象异步处理对象以及对其进行各类操做的组件;vue

Promise很重要!很重要!很重要!对,要强调三遍,必定要好好掌握。java

实例(假如此处你还不是很了解,不要紧,先留个印象):react

var promise = new Promise((resolve,reject) => {
    if(true) { resolve(100) };
    if(false) { reject('error') };
});
//使用
promise.then(value => {
    console.log(value);    //100
}).catch(error => {
    console.error(error);
});
复制代码

**注意⚠️*本文中的函数表达式均采用ES6的箭头函数表达式的语法,你若还不是很清楚,请自行查阅(参考阮一峰ECMAScript 6入门)[1]。es6

回调函数

刚刚咱们说,Promise解决了ajax请求过程当中的回调地狱的问题,那么回调(函数)是什么,为何要用到回调(函数),咱们一块儿回顾一下。ajax

回调函数(callback function),也被称做高阶函数。编程

就是把一个函数B做为参数(注意:是做为参数)传入“另外一个函数A”中,而后这个函数B在“另外一个函数A”中调用,那么这个函数B,就叫回调函数。函数A执行完之后执行函数B,这个过程就叫作回调。json

注意:回调函数不是当即就执行。它是在另外一个函数执行完成以后被调用,即在包含的函数体中指定的地方“回头调用”。小程序

也就是:A(主函数)让 B(参数)作事,B作着作着,信息不够,不知道怎么作了,就须要A告诉他,这时,A到外面获取信息,待A执行完毕后拿到了所需信息,再回过头来调用B。segmentfault

网上也有一个通俗易懂的例子帮助理解回调函数:

你到一个商店买东西,恰好你要的东西没有货,因而你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,而后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫作触发了回调关联的事件,店员给你打电话叫作调用回调函数,你到店里去取货叫作响应回调事件。

回调函数的使用场景:主要是须要将父函数的执行结果通知给回调函数进行处理时使用。

//回调函数举例1
$("#btn").click(() => {
  alert("我是回调函数里的内容,点击后才出现,并无当即执行");
});

//回调函数举例2
function runAsyncMain(callback){
    callback();
    console.log('我是主函数');  
}
function runAsyncCallback(){
    setTimeout(() => {
        console.log('我是回调函数');
    }, 2000);    //此处模拟延迟加载
}
runAsyncMain(runAsyncCallback);  //(先输出)我是主函数    (2s后输出) 我是回调函数
复制代码

可见此处主函数runAsyncMain执行的过程当中,按顺序本应先执行回调函数,但输出结果倒是后输出的回调函数内容,这说明,主函数不用等回调函数执行完再执行后续的语句,能够接着执行本身的代码,等回调函数准备好,再执行回调函数。所谓的异步加载也不过如此,固然,异步与回调并无直接的联系,回调只是异步的一种实现方式。

为何须要Promise?

介绍完回调函数,要回到Promise的主场了。程序执行过程当中,有很是多的应用场景咱们不能当即知道应该如何继续往下执行,例如很重要的ajax请求。通俗来讲,因为网速的不一样,可能你获得返回值的时间也不一样,这时咱们就须要等某个结果出来了以后才知道怎么样继续下去,例以下方的回调函数案例:

// 需求:当一个ajax结束后,获得的值,须要做为另一个ajax的参数被使用(即该参数得从上一个ajax请求中获取)
var url = 'XXXXXX';
var result;

var XHR = new XMLHttpRequest();
XHR.open('GET', url, true);    //第一个ajax请求
XHR.send();

XHR.onreadystatechange = function() {
    if (XHR.readyState == 4 && XHR.status == 200) {
        result = XHR.response;
        console.log(result);
        // 伪代码
        var url2 = 'XXXXXX' + result.someParams;    //经过第一个ajax请求的结果,获得第二个ajax请求所须要的url
        var XHR2 = new XMLHttpRequest();
        XHR2.open('GET', url2, true);      //第二个ajax请求
        XHR2.send();
        XHR2.onreadystatechange = function() {
            ...          
            //往复上述过程,会获得第三个ajax请求所须要的url值,而后进行第三个ajax请求,在获得第四个……  超恐怖
        }
    }
}
复制代码

当上述需求中出现第三个ajax(甚至更多)仍然依赖上一个请求的时候,代码就会变成一场灾难。也就是咱们常说的回调地狱

这时,咱们可能会但愿:

  1. 让代码变得更具备可读性和可维护性,减轻一层层套用数据和请求的现象;

  2. 将请求和数据处理明确的区分开。

这时Promise就要闪亮登场了,Promise中强大的then方法,能够解决刚刚出现的恐怖的回调地狱问题,而且让代码更优雅。

别急,咱们先从文档中最基础的 API 入手。

Promise的API

一、constructor (构造函数属性)

Promise自己也是一个构造函数,须要经过这个构造函数建立一个新的Promise对象做为接口,使用new来调用Promise的构造器来进行实例化,因此这个实例化出来的新对象:具备constructor属性,而且指针指向他的构造函数Promise。

var promise = new Promise((resolve, reject) => {
    // 此处代码会当即执行
    // 当调用栈内容处理结束后,再经过promise.then()方法调用resolve 或 reject返回的数据
});
复制代码

二、Instance Method (实例方法)

promise.then()

Promise对象中的promise.then(resolve,reject)实例方法,能够接收构造函数中处理的状态变化,经过此方法,设置了其值在resolve(成功)/reject(失败)时调用的回调函数,并分别对应执行。

promise.then(onFulfilled, onRejected)
复制代码

then方法有2个参数(都是可选参数,此参数是个回调函数):

  • resolve成功时onFulfilled会被调用;

  • reject失败时onRejected会被调用。

promise.then成功和失败时均可以使用,而且then方法的执行结果也会返回一个Promise对象

promise.catch()

另外在只想对异常进行处理时能够采用promise.then(undefined, onRejected)这种方式,只指定reject时的回调函数便可。不过这种状况下 promise.catch(onRejected)应该是个更好的选择。

promise.catch(onRejected)
复制代码

注意:在IE8及如下版本,使用promise.catch()的代码,会出现identifier not found的语法错误。(由于catch与ECMAScript的保留字[2](Reserved Word)有关,在ECMAScript 3中保留字是不能做为对象的属性名使用的。)

解决办法:不单纯的使用catch,而是使用then来避免这个问题。

//then和catch方法 举例
function asyncFunction(value) {
    var p = new Promise((resolve, reject) => {
        if(typeof(value) == 'number'){
            resolve("我是数字");
        }else {
            reject("我不是数字");
        }
    });
    return p;
}

// 写法1:同时使用then和catch方法
asyncFunction(123).then(value => {  
    console.log(value);   
}).catch(error => {
    console.log(error);
});
//执行结果:我是数字
//解说:执行过程 
// 一、在asyncFunction这个方法中建立一个Promise构造函数p,只要执行asyncFunction()就会返回一个Promise对象;
// 二、asyncFunction方法表示:若是传入的参数value值是数字,就输出"我是数字",若是不是数字,就输出"我不是数字"
// 三、写法1中,为promise对象用设置 .then 方法调用返回值时的回调函数,.catch 方法来设置发生错误时的回调函数
// 四、promise对象会先判断resolve,并执行then方法,若是resolve执行经过,则不会执行catch方法,若上面代码在执行中产生异常,在 catch 中设置的回调函数就会被执行,并输出error。

// 写法2:只使用 then方法,不使用catch 方法
asyncFunction('abc').then((value) => {  
    console.log(value);   
},(error) => {
    console.log(error);
});
//执行结果:我不是数字
复制代码

三、Static Method (静态方法)

Promise这样的全局对象还拥有一些静态方法,后文中会有详细解释。

Promise.resolve()

Promise.resolve()方法返回一个已给定值解析后的新的Promise对象,从而能继续使用then的链式方法调用。

Promise.reject()

Promise.reject()方法和Promise.resolve()方法同样。

Promise.all()

Promise.all()方法的做用是将多个Promise对象实例包装,生成并返回一个新的Promise实例。

Promise.race()

Promise.race()与Promise.all()相反。

Promise的状态 (Fulfilled、Rejected、Pending)

Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数可以及时调用。

new Promise实例化的promise对象有如下三个状态:

  • "unresolved" - Pending| 既不是resolve也不是reject的状态。等待中,或者进行中,表示Promise刚建立,尚未获得结果时的状态;

  • "has-resolution" - Fulfilled| resolve(成功)时。此时会调用onFulfilled

  • "has-rejection" - Rejected| reject(失败)时。此时会调用onRejected

关于下面这三种状态的读法,其中左侧为在ES6 Promises规范中定义的术语,而右侧则是在Promises/A+中描述状态的术语。

上图的意思是promise对象的状态,从_Pending_转换为_Fulfilled_或_Rejected_以后, 这个promise对象的状态就不会再发生任何变化,会一直保持这个结果。

当promise的对象状态发生变化时,用.then来定义只会被调用一次的函数。

Promise的使用

一、建立Promise对象

前面不少次强调,Promise自己就是一个构造函数,因此能够经过new建立新的Promise对象:

var p = new Promise((resolve, reject) => {
    //作一些异步操做
    setTimeout(() => {
        console.log('执行完成');
        resolve('个人数据');
    }, 0);
    console.log("我先执行")
});
//先输出:我先执行
//1秒以后输出:执行完成
复制代码

咱们执行了一个异步操做,也就是setTimeout,1秒后,输出“执行完成”,而且调用resolve方法。可是只是new了一个Promise对象,并无调用它,咱们传进去的函数就已经执行了。为了不这个现象产生,因此咱们用Promise的时候通常是包在一个函数中,须要的时候去运行这个函数。

若是你对执行的前后顺序还不理解,推荐阅读文章事件的循环机制(Event loop)[3]前文中咱们也曾屡次提到异步加载,因此此概念应熟记于心。

异步任务:指不进入主线程、而进入"任务队列"(task queue)的任务,只有等主线程任务执行完毕,“任务队列”开始通知主线程,请求执行任务,该任务才会进入主线程执行。

异步加载例如:你烧壶水要冲咖啡,但是水要10分钟才能烧开,此时,你转身去写了个小程序,等10分钟后水好了,才回来继续冲咖啡的活动,中间你去作了不少别的有意义的事情,也并无耽误冲咖啡这项任务,这就是异步。

也能够理解为能够改变程序正常执行顺序的操做就能够当作是异步操做。例如setTimeout和setInterval函数。

同步任务:指在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;

同步加载例如:你烧壶水要冲咖啡,但是水要10分钟才能烧开,此时,你就等啊等啊等,等了10分钟水好了,才继续冲咖啡的活动,中间的过程就是等待,啥都不干,这就是同步。

推荐参考文章:完全理解同步、异步和事件循环(Event Loop)[4]

你会发现,异步加载的举例和上文中强调的回调函数例子很像,但刚刚也强调了,异步与回调并无直接的联系,回调只是异步的一种实现方式(再次重复,加深理解)。

你会不会觉得异步像是多线程操做那样并列执行程序?我想说,千万不要这样想!js就是单线程,没有多线程一说,因此不存在并行,即使是异步,也是单线程,只不过是放在了异步队列里,对,是队列,假若你不是很理解,那么请前去了解一下事件循环机制中的****宏任务和微任务(promise就是微任务,settimeout是宏任务,很是不错的一篇文章)的区别,它们所在的队列是不一样的,看过以后,相信你会对promise有更深入的了解。

二、封装Promise对象

function asyncFunction(num) {
    var p = new Promise((resolve, reject) => {  //建立一个Promise的新对象p
        if (typeof num == 'number') {
            resolve();
        } else {
            reject();
        }
    });
    p.then(function() {  //这个(第一个)function是resolve对应的参数
        console.log('这个是数字');
    }, function() {    //这个(第二个)function是reject对应的参数
        console.log('我不是数字');
    })
    return p;  //此处返回对象p
}

//执行这个函数咱们获得了一个Promise构造出来的对象p,因此p.__proto__ === Promise.prototype,即p的指针指向了构造函数Promise,所以asyncFunction()可以使用Promise的属性和方法

//此种写法能够屡次调用asyncFunction这个方法
asyncFunction('hahha');  //我不是数字
asyncFunction(1234);  //这个是数字
复制代码

咱们刚刚讲到,then方法的执行结果也会返回一个Promise对象,获得一个结果。所以咱们能够进行then的链式执行,接收上一个then返回回来的数据并继续执行,这也是解决回调地狱的主要方式。

三、Promise的链式操做和数据传递

下面咱们就来看看.then和.catch这两个方法返回的究竟是不是新的promise对象。

var aPromise = new Promise(resolve => {
    resolve(100);
});// aPromise为原promise对象
// 下面分开进行.then和.catch操做
var thenPromise = aPromise.then(value => {
    console.log(value);
});
var catchPromise = thenPromise.catch(error => {
    console.error(error);
});
console.log(aPromise !== thenPromise); // => true
console.log(aPromise !== catchPromise); // => true
console.log(thenPromise !== catchPromise);// => true
复制代码

===是严格相等比较运算符,咱们能够看出这三个对象都是互不相同的,这也就证实了thencatch都返回了和调用不一样的promise对象。咱们经过下面这个例子进一步来理解:

// 1: 对同一个promise对象同时调用 `then` 方法
var aPromise = new Promise(resolve => {
    resolve(100);
});
aPromise.then(value => {
    return value * 2;
});
aPromise.then(value => {
    return value * 2;
});
aPromise.then(value => {
    console.log("1: " + value); // 1: 100
})

// vs

// 2: 对 `then` 进行 promise 链式 方式进行调用
var bPromise = new Promise(resolve => {
    resolve(100);
});
bPromise.then(value => {
    return value * 2;
}).then(value => {
    return value * 2;
}).then(value => {
    console.log("2: " + value); // 2: 400
});
复制代码

写法1 中并无使用promise的方法链方式,这在Promise中应该是极力避免的写法。这种写法中的then调用几乎是同时开始执行的,并且传给每一个then方法的value值都是100

写法2 则采用了方法链的方式将多个then方法调用串连在了一块儿,各函数也严格按照 resolve → then → then → then 的顺序执行,而且传给每一个then方法的value的值都是前一个promise对象经过return返回的值,实现了Promise的数据传递。

强调:promise的链式操做实现了数据的传递,promise非链式操做的方法没法实现数据传递。

四、经过Promise封装ajax解决回调地狱问题

刚刚在开篇(【为何须要Promise】 这一节),经过一个ajax的例子,引出了回调地狱的概念,强调了经过回调函数方式解决多级请求都依赖于上一级数据时所引起的问题。下面咱们经过刚刚学习过的Promise内容(特别是.then的链式数据传递)对上面的ajax数据依赖的案例进行重写:

var url = 'XXXXX';

// 封装一个get请求的方法
function getJSON(url) {
    return new Promise((resolve, reject) => {
        var XHR = new XMLHttpRequest();
        XHR.open('GET', url, true);
        XHR.send();

        XHR.onreadystatechange = function() {
            if (XHR.readyState == 4) {
                if (XHR.status == 200) {
                    try {
                        var response = JSON.parse(XHR.responseText);
                        resolve(response);
                    } catch (e) {
                        reject(e);
                    }
                } else {
                    reject(new Error(XHR.statusText));
                }
            }
        }
    })
}

getJSON(url)
    .then(resp => {
        console.log(response);
        return url2 = 'http:xxx.yyy.com/zzz?ddd=' + resp;
    })
    .then(resp => {
        console.log(response);
        return url3 = 'http:xxx.yyy.com/zzz?ddd=' + resp;
    });
复制代码

new Promise写法的快捷方式

一、Promise.resolve

new Promise(resolve => {
    resolve(100);
});
// 等价于
Promise.resolve(100);  //Promise.resolve(100); 能够认为是上述代码的语法糖。

// 使用方法
Promise.resolve(100).then(value => {
    console.log(value);
});
复制代码

另:Promise.resolve方法另外一个做用就是将thenable对象转换为promise对象。

ES6 Promises提到了Thenable这个概念,简单来讲它就是一个很是相似promise的东西。就像咱们有时称具备.length方法的非数组对象为类数组(Array like)同样,thenable指的是一个具备.then方法的对象。

由于:类库没有提供Promise的实现,用户经过Promise.resolve(thenable)来本身实现了Promise,而且,做为Promise使用的时候,须要和Promise.resolve(thenable)一块儿配合使用,将thenable对象转换promise对象:

var promise = Promise.resolve($.ajax('/json/comment.json'));// => promise对象
promise.then(function(value){
   console.log(value);
});
复制代码

在此再也不对Thenable进行过多赘述,可自行了解。

二、Promise.reject

new Promise((resolve,reject) => {
    reject(new Error("出错了"));
});
// 等价于
 Promise.reject(new Error("出错了"));  // Promise.reject(new Error("出错了")) 就是上述代码的语法糖。

// 使用方法
Promise.reject(new Error("BOOM!")).catch(error => {
    console.error(error);
});
复制代码

Promise.all()

Promise.all接收一个promise对象的数组做为参数,当这个数组里的全部promise对象所有变为resolve或reject状态的时候,它才会去调用.then方法。

也就是说:Promise的all方法提供了异步操做的能力,而且在全部异步操做执行完后才执行回调。

// `delay`毫秒后执行resolve
function timerPromisefy(delay) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(delay);
        }, delay);
    });
}
var startDate = Date.now();
// 全部promise变为resolve后程序退出
Promise.all([
    timerPromisefy(1),
    timerPromisefy(32),
    timerPromisefy(64),
    timerPromisefy(128)
]).then(values => {
    console.log(Date.now() - startDate + 'ms');
    // 约128ms
    console.log(values);    // [1,32,64,128]
});
复制代码

这说明timerPromisefy会每隔一、3二、6四、128ms都会有一个promise发生resolve行为,返回一个promise对象,状态为FulFilled,其状态值为传给timerPromisefy的参数,而且all会把全部异步操做的结果放进一个数组中传给then。

从上述结果能够看出,传递给Promise.all的promise并非一个个的顺序执行的,而是同时开始、并行执行的。

Promise.race()

all方法的效果其实是「谁跑的慢,以谁为准执行回调」,那么相对的就有另外一个方法「谁跑的快,以谁为准执行回调」,这就是race方法,这个词原本就是赛跑的意思。race的用法与all同样,接收一个promise对象数组为参数。

Promise.all在接收到的全部的对象promise都变为FulFilled或者Rejected状态以后才会继续进行后面的处理,与之相对的是Promise.race只要有一个promise对象进入FulFilled或者Rejected状态的话,就会继续进行后面的处理。

// `delay`毫秒后执行resolve
function timerPromisefy(delay) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(delay);
        }, delay);
    });
}
// 任何一个promise变为resolve或reject 的话程序就中止运行
Promise.race([
    timerPromisefy(1),
    timerPromisefy(32),
    timerPromisefy(64),
    timerPromisefy(128)
]).then(function (value) {
    console.log(value);    // => 1
});
复制代码

上面的代码建立了4个promise对象,这些promise对象会分别在1ms、32ms、64ms和128ms后变为肯定状态,即FulFilled,而且在第一个变为肯定状态的1ms后,.then注册的回调函数就会被调用,这时候肯定状态的promise对象会调用resolve(1)所以传递给value的值也是1,控制台上会打印出1来。

promise的基本使用原理以及它在实际应用中为咱们解决的问题,在上述过程当中已经介绍完了,你是否理解了呢?学习是一个反复阅读,反复加深印象的过程,加油紧紧掌握这一知识点,在vue、react等框架的使用中,也会频繁用到有关promise的知识,下面一块儿来检测一下对promise的认知结果吧。

小练习

下面内容的输出结果应该是啥?

function test1() {
    console.log("test1");
}
function test2() {
    console.log("test2");
}
function onRejected(error) {
    console.log("捕获错误: test1 or test2", error);
}
function test3() {
    console.log("end");
}

var promise = Promise.resolve();
promise
    .then(test1)
    .then(test2)
    .catch(onRejected)
    .then(test3);
复制代码

舒适提示:这里没有为then方法指定第二个参数(onRejected)。

补充1:对比 callback → Promise → async/await

javascript的异步发展历程,从callback,到Promise对象、Generator函数,不停地优化程序上的编写方式,但又让人以为不是很完全,随即又有了以后的async/await的异步编程方式,让异步编程变得更像同步代码,加强了代码的可读性,甚至不少人评价async/await是异步操做的终极解决方案,接下来简单介绍一下这三种方式各自的优缺点:

1.callback(回调):本文开篇也说起了回调函数虽然好理解,但只对于简单的异步程序,callback是能够胜任的,可是在ajax须要被屡次调用时使用起来仍是会产生不少问题:

  • 高耦合,让程序变得难以维护;

  • 而且错误捕捉要经过人工的设置判断来进行。

2.Promise:ES6提供的构造函数Promise的实现是要基于callback的,解决了异步执行的问题:

  • 经过Promise.then()链式调用的方法,解决了回调函数层层嵌套(回调地狱)的问题,让代码和操做都变得更加简洁;

  • 能够统一经过Promise.catch()方法对异常进行捕获,无需再像callback那样,为每一个异步操做添加异常处理;

  • Promise.all()方法能够对异步操做进行并行处理,同时执行多个操做。

但Promise也存在缺点:

  • 当处于未完成状态时,没法肯定目前处于哪一阶段;

  • 若是不设置回调函数,Promise内部的错误不会反映到外部;

  • Promise一旦新建它就会当即执行,没法中途取消。

ES6中,还有一个generator函数,之前一个函数中的代码要么被调用,要么不被调用,不存在能暂停的状况,generator函数让代码能够中途暂停、异步执行,它与Promise的结合使用,相似于async/await(见下文)效果的代码。

整个Generator函数就是一个封装的异步任务的容器。它的语法是在函数名前加个*号,在异步操做须要暂停的地方,都用yield语句注明,但仅有yield,函数是不会执行的,他须要调用next方法,指针都会向下移一个状态,直到遇到下一个yield表达式(或return语句)为止。

3**.async/await**:ES7中新增的异步编程方法,async/await的实现是基于 Promise的,简单而言就是**async function就是返回Promise的function,是generator的语法糖,其实async函数就是将Generator函数的星号(*)替换成 async,将yield替换成await。**不少人认为async/await是异步操做的终极解决方案:

  • 改进JS中异步操做串行执行的代码组织方式,减小callback的嵌套;

  • 语法简洁,更像是同步代码,也更符合普通的阅读习惯;

  • Promise中不能自定义使用try/catch进行错误捕获,可是在Async/await中能够像处理同步代码处理错误。

综上异步编程的演变过程可见,它语法目标,其实就是怎样让它更像同步编程。

若是你想要更详细的了解Generator和async/await异步操做方式,请查阅MDN中的相关文档。同时推荐一篇通俗易懂的文章:www.lazycoffee.com/articles/vi…

补充2:Promise 的实现原理

一块儿回顾一下, 前面咱们介绍了promise的来由、解决的问题、经常使用和重点的方法、以及promise的使用方法和应用场景,你是否是也很好奇,没有promise的话,咱们要如何模拟出promise呢?也就是,promise是如何实现的?

回顾一下Promise的使用过程:

**1.**首先要知道,Promise对象有三个状态:Pending(进行中)Fulfilled(已成功)Rejected(已失败),因此须要Promise设置三个状态值;

**2.**Promise存在resolve和reject两个回调函数做为自身参数:new Promise((resolve, reject){});,经过判断步骤1中的三个状态值,来肯定输出哪一个方法,例如:

  • promise处于Fulfilled状态时,就输出resolve对应的方法;

  • Rejected状态时就输出reject方法。

**3.**Promise的then方法要接收两个参数:promise.then(onFulfilled, onRejected),onFulfilled和onRejected也必须是两个函数:

  • 当Promise的状态为成功时,调用onFulfilled这个方法,其中onFulfilled方法中的参数是步骤2中promise成功状态 resolve执行时传入的值;

  • 当Promise的状态为失败时,调用onRejected这个方法,其中onRejected方法中的参数是步骤2中promise失败状态reject执行时传入的值。

**4.**若是then被同一个Promise屡次调用,全部onFulfilledonRejected需按照其注册顺序依次回调;

5.……

**6.**固然,Promise还有.catch、.all、.race等不少方法,以及基本逻辑和规则肯定后,还须要加上错误捕获、值传递等机制,例如判断步骤2中的回调函数是否为function、步骤4链式操做中,then返回的是否为一个新的Promise对象等等;

具体的Promise实现原理代码以下,如有兴趣的童鞋可参考阅读,帮助进一步加深理解:

// 判断变量否为function
  const isFunction = variable => typeof variable === 'function'
  // 定义Promise的三种状态常量
  const PENDING = 'PENDING'
  const FULFILLED = 'FULFILLED'
  const REJECTED = 'REJECTED'

  class MyPromise {
    constructor (handle) {
      if (!isFunction(handle)) {
        throw new Error('MyPromise must accept a function as a parameter')
      }
      // 添加状态
      this._status = PENDING
      // 添加状态
      this._value = undefined
      // 添加成功回调函数队列
      this._fulfilledQueues = []
      // 添加失败回调函数队列
      this._rejectedQueues = []
      // 执行handle
      try {
        handle(this._resolve.bind(this), this._reject.bind(this)) 
      } catch (err) {
        this._reject(err)
      }
    }
    // 添加resovle时执行的函数
    _resolve (val) {
      const run = () => {
        if (this._status !== PENDING) return
        // 依次执行成功队列中的函数,并清空队列
        const runFulfilled = (value) => {
          let cb;
          while (cb = this._fulfilledQueues.shift()) {
            cb(value)
          }
        }
        // 依次执行失败队列中的函数,并清空队列
        const runRejected = (error) => {
          let cb;
          while (cb = this._rejectedQueues.shift()) {
            cb(error)
          }
        }
        /* 若是resolve的参数为Promise对象,则必须等待该Promise对象状态改变后,
          当前Promsie的状态才会改变,且状态取决于参数Promsie对象的状态
        */
        if (val instanceof MyPromise) {
          val.then(value => {
            this._value = value
            this._status = FULFILLED
            runFulfilled(value)
          }, err => {
            this._value = err
            this._status = REJECTED
            runRejected(err)
          })
        } else {
          this._value = val
          this._status = FULFILLED
          runFulfilled(val)
        }
      }
      // 为了支持同步的Promise,这里采用异步调用
      setTimeout(run, 0)
    }
    // 添加reject时执行的函数
    _reject (err) { 
      if (this._status !== PENDING) return
      // 依次执行失败队列中的函数,并清空队列
      const run = () => {
        this._status = REJECTED
        this._value = err
        let cb;
        while (cb = this._rejectedQueues.shift()) {
          cb(err)
        }
      }
      // 为了支持同步的Promise,这里采用异步调用
      setTimeout(run, 0)
    }
    // 添加then方法
    then (onFulfilled, onRejected) {
      const { _value, _status } = this
      // 返回一个新的Promise对象
      return new MyPromise((onFulfilledNext, onRejectedNext) => {
        // 封装一个成功时执行的函数
        let fulfilled = value => {
          try {
            if (!isFunction(onFulfilled)) {
              onFulfilledNext(value)
            } else {
              let res =  onFulfilled(value);
              if (res instanceof MyPromise) {
                // 若是当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
                res.then(onFulfilledNext, onRejectedNext)
              } else {
                //不然会将返回结果直接做为参数,传入下一个then的回调函数,并当即执行下一个then的回调函数
                onFulfilledNext(res)
              }
            }
          } catch (err) {
            // 若是函数执行出错,新的Promise对象的状态为失败
            onRejectedNext(err)
          }
        }
        // 封装一个失败时执行的函数
        let rejected = error => {
          try {
            if (!isFunction(onRejected)) {
              onRejectedNext(error)
            } else {
                let res = onRejected(error);
                if (res instanceof MyPromise) {
                  // 若是当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
                  res.then(onFulfilledNext, onRejectedNext)
                } else {
                  //不然会将返回结果直接做为参数,传入下一个then的回调函数,并当即执行下一个then的回调函数
                  onFulfilledNext(res)
                }
            }
          } catch (err) {
            // 若是函数执行出错,新的Promise对象的状态为失败
            onRejectedNext(err)
          }
        }
        switch (_status) {
          // 当状态为pending时,将then方法回调函数加入执行队列等待执行
          case PENDING:
            this._fulfilledQueues.push(fulfilled)
            this._rejectedQueues.push(rejected)
            break
          // 当状态已经改变时,当即执行对应的回调函数
          case FULFILLED:
            fulfilled(_value)
            break
          case REJECTED:
            rejected(_value)
            break
        }
      })
    }
    // 添加catch方法
    catch (onRejected) {
      return this.then(undefined, onRejected)
    }
    // 添加静态resolve方法
    static resolve (value) {
      // 若是参数是MyPromise实例,直接返回这个实例
      if (value instanceof MyPromise) return value
      return new MyPromise(resolve => resolve(value))
    }
    // 添加静态reject方法
    static reject (value) {
      return new MyPromise((resolve ,reject) => reject(value))
    }
    // 添加静态all方法
    static all (list) {
      return new MyPromise((resolve, reject) => {
        /**
         * 返回值的集合
         */
        let values = []
        let count = 0
        for (let [i, p] of list.entries()) {
          // 数组参数若是不是MyPromise实例,先调用MyPromise.resolve
          this.resolve(p).then(res => {
            values[i] = res
            count++
            // 全部状态都变成fulfilled时返回的MyPromise状态就变成fulfilled
            if (count === list.length) resolve(values)
          }, err => {
            // 有一个被rejected时返回的MyPromise状态就变成rejected
            reject(err)
          })
        }
      })
    }
    // 添加静态race方法
    static race (list) {
      return new MyPromise((resolve, reject) => {
        for (let p of list) {
          // 只要有一个实例率先改变状态,新的MyPromise的状态就跟着改变
          this.resolve(p).then(res => {
            resolve(res)
          }, err => {
            reject(err)
          })
        }
      })
    }
    finally (cb) {
      return this.then(
        value  => MyPromise.resolve(cb()).then(() => value),
        reason => MyPromise.resolve(cb()).then(() => { throw reason })
      );
    }
  }

// 源码连接:https://juejin.im/post/5b83cb5ae51d4538cc3ec354
复制代码

参考:

[1].阮一峰ECMAScript 6入门(es6.ruanyifeng.com/#docs/funct…)

[2].保留字(mothereff.in/js-properti…)

[3].事件的循环机制(Event loop)(www.jianshu.com/p/12b9f73c5…)~~

[4].完全理解同步、异步和事件循环(Event Loop)(segmentfault.com/a/119000000…) (developer.mozilla.org/zh-CN/docs/…)

[5].JavaScript Promise迷你书(liubin.org/promises-bo…)

[6].透彻掌握Promise的使用(www.jianshu.com/p/fe5f17327…)

相关文章
相关标签/搜索