深入理解ES6--11.Promise和异步编程

原创文章&经验总结&从校招到A厂一路阳光一路沧桑

详情请戳www.codercc.com

image

主要知识点:Promise生命周期、Promise基本操作、Promise链、响应多个Promise以及集成Promise

Promise与异步编程.png

1. Promise基础

什么是回调地狱?

当使用回调函数来进行事件处理的时候,如果嵌套多层回调函数的时候,就会出现回调地狱,例如:

method1(function(err, result) {
    if (err) {
        throw err;
    } 
    method2(function(err, result) {
        if (err) {
            throw err;
        } 
        method3(function(err, result) {
            if (err) {
                throw err;
            } 
            method4(function(err, result) {
                if (err) {
                    throw err;
                } 
                method5(result);
            });
        });
    });
});

像本例一样嵌套多个方法调用会创建错综复杂的代码,会难以理解与调试。当想要实现更复
杂的功能时,回调函数也会存在问题。要是你想让两个异步操作并行运行,并且在它们都结
束后提醒你,那该怎么做?要是你想同时启动两个异步操作,但只采用首个结束的结果,那
又该怎么做?而使用Promise就能避免回调地狱的情况。

Promise可以当做是一个占位符,表示异步操作的执行结果。函数可以返回一个Promise,而不必订阅一个事件或者向函数传递一个回调函数。

Promise的生命周期

每个 Promise 都会经历一个短暂的生命周期,初始为挂起状态(pending state) ,这表示异步操作尚未结束。一个挂起的 Promise 也被认为是未决的(unsettled )。一旦异步操作结束, Promise就会被认为是已决的(settled ) ,并进入两种可能状态之一:

  1. 已完成(fulfilled ) : Promise 的异步操作已成功结束;
  2. 已拒绝(rejected ) : Promise 的异步操作未成功结束,可能是一个错误,或由其他原因导致。

内部的[[PromiseState]] 属性会被设置为"pending""fulfilled" 或 "rejected",以反映Promise的状态。该属性并未在 Promise 对象上被暴露出来,因此你无法以编程方式判断 Promise 到底处于哪种状态。不过你可以使用then()方法在 Promise 的状态改变时执行一些特定操作。

  1. then()方法

    then()方法在所有的 Promise 上都存在,并且接受两个参数。第一个参数是 Promise 被完成时要调用的函数,异步操作的结果数据都会被传入这个完成函数。第二个参数则是 Promise 被拒绝时要调用的函数,与完成函数相似,拒绝函数会被传入与拒绝相关联的任何附加数据。then()方法的两个参数是可选的,因此可以自由组合监听完成和失败的处理函数;

  2. catch()方法

    Promise有catch()方法,等同于只传递拒绝处理函数给then()方法:

     promise.catch(function(err) {
         // 拒绝
         console.error(err.message);
     });
     // 等同于:
     promise.then(null, function(err) {
         // 拒绝
         console.error(err.message);
     });
    

创建未决的Promise

使用Promise构造器可以创建一个Promise实例,此构造器接收一个参数:一个被称之为执行器(excutor)的函数,该函数包含了resolve()函数和reject()函数这两个参数。resolve()函数在异步任务执行成功时调用,而reject()函数在异步任务执行失败时调用。例如:

let promise = new Promise(function(resolve,reject){
    console.log('hi, promise');
    resolve();

});

promise.then(()=>{
    console.log('hi, then');

});

console.log('hi');

输出:
hi, promise
hi
hi then

从输出结果可以看出,Promise构造器中的代码是最先执行的,而then()代码是最后执行的,这是因为只有在Promise中的处理器函数执行结束之后,then()方法中的完成处理函数或者拒绝处理函数才会添加到作业队列的尾部。

创建已决的Promise

  1. 使用Promise.resolve()

Promise.resolve()方法接收一个参数,并会返回一个处于已完成状态的 Promise ,在then()方法中使用完成处理函数才能提取该完成态的Promise传递的值,例如:

let promise = Promise.resolve('hi');
promise.then((value)=>{
    console.log(value); //hi
});
  1. 使用Promise.reject()

可以使用Promise.reject()方法来创建一个已拒绝状态的Promise,同样只有在拒绝处理函数中或者catch()方法中才能接受reject()方法传递的值:

let reject = Promise.reject('reject');

reject.catch((value)=>{
    console.log(value); //reject
})

非Promise的thenable

当一个对象拥有一个能接受resolvereject参数的then()方法时,该对象就会被认为是一个非Promisethenable,例如:

let thenable = {

    then:function(resolve,reject){
        resolve('hi');
    }
}

Promise.resolve()Promise.reject()方法都能够接受非Promise的thenable作为参数,当传入了非Promise的thenable时,这些方法会创建一个新的Promise,并且可以使用then()方法对不同状态进行操作:

创建一个已完成的Promise

let thenable = {

    then:function(resolve,reject){
        resolve('hi');
    }
}

let promise = Promise.resolve(thenable);
promise.then((value)=>{
    console.log(value); //hi
});

同样利用thenable可以创建一个已拒绝的Promise:

let thenable = {

    then:function(resolve,reject){
        reject('hi');
    }
}

let promise = Promise.resolve(thenable);
promise.then(null,(value)=>{
    console.log(value);
});

执行器错误

当执行器内部抛出错误,那么Promise的拒绝处理函数就会被调用,例如:

let promise = new Promise(function(resolve,reject){
    throw new Error('Error!');

})

promise.catch(function(msg){
    console.log(msg); //error
})

2. Promise链

除了使用单个Promise外,多个Promise可以进行级联使用,实际上then()方法或者catch()方法会返回一个新的Promise,仅当前一个Promise被决议之后,后一个Promise才会进行处理。

串联Promise

let p1 = new Promise(function(resolve,reject){
    resolve('hi');
});

p1.then((value)=>{
    console.log(value);
    throw new Error('Error!');
}).catch(function(error){
    console.log(error);
})

可以看出当p1的then()方法执行结束后会返回一个Promise,因此,在此基础上可以继续执行catch()方法。同时,Promise链允许捕获前一个Promise的错误

Promise链中传递值

Promise链的另一个重要方面是能从一个Promise传递数据给另一个Promise的能力。前一个Promise的完成处理函数的返回值,传递到下一个Promise中。

//Promise链传递值

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

p1.then(value=>value+1)
.then(value=>{
    console.log(value);
})

p1的完成处理函数返回了value+1,也就是2,会传入到下一个Promise的完成处理函数,因此,第二个then()方法中的完成处理函数就会输出2。拒绝处理函数同样可以被用于在Promise链中传递数据。

Promise链中传递Promise

在完成或者拒绝处理函数中可以返回基本类型值,从而可以在Promise链中传递。另外,在Promise链中也可以传递对象,如果传递的是Promise对象,就需要额外的处理:

传递已完成状态的Promise

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

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

p1.then(value=>{
    console.log(value);
    return p2;
}).then(value=>{
    console.log(value);
});
输出:1  2

p1中返回了Promise对象p2,当p2完成时,才会调用第二个then()方法,将值value传到完成处理函数中。若Promise对象p2被拒绝后,第二个then()方法中的完成处理函数就不会执行,只能通过拒绝处理函数才能接收到p2传递的值:

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

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

p1.then(value=>{
    console.log(value);
    return p2;
}).catch(value=>{
    console.log(value);
});

3. 响应多个Promise

如果想监视多个Promise的状态,从而决定下一步动作,可以使用ES6提供的两个方法:Promise.all()Promise.race()

Promise.all()

Promise.all()方法能接受单个可迭代对象(如数组)作为参数,可迭代对象的元素都是Promise。该方法会返回一个Promise,只有传入所有的Promise都已完成,所返回的Promise才会完成,例如:

//Promise.all()
let p1 = new Promise(function(resolve,reject){
    resolve(1);
})

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

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

let p4 = Promise.all([p1,p2,p3]);
p4.then(value=>{
    console.log(Array.isArray(value)); //true
    console.log(value); //[1,2,3]
})

Promise.all() 的调用创建了新的Promise p4,在 p1p2p3 都被完成后, p4 最终会也被完成。传递给 p4 的完成处理函数的结果是一个包含每个决议值(1 、 2 与 3 ) 的数组,这些值的存储顺序保持了待决议的 Promise 的顺序(与完成的先后顺序无关) ,因此你可以将结果匹配到每个Promise

若传递给Promise.all() 的某个 Promise 被拒绝了,那么方法所返回的 Promise 就会立刻被拒绝,而不必等待其他的 Promise 结束

//Promise.all()
let p1 = new Promise(function(resolve,reject){
    resolve(1);
})

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

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

let p4 = Promise.all([p1,p2,p3]);
p4.catch(value=>{
    console.log(Array.isArray(value)); //true
    console.log(value); //2
})

在此例中, p2 被使用数值 2 进行了拒绝,则 p4 的拒绝处理函数就立刻被调用,而不会
等待 p1 或 p3 结束执行(它们仍然会各自结束执行,只是 p4 不等它们) 。

拒绝处理函数总会接受到单个值,而不是一个数组。该值是被拒绝的Promise所返回的拒绝值。

Promise.race()

Promise.race()方法接收一个元素是Promise的可迭代对象,并返回一个新的Promise。一旦传入Promise.race()的可迭代对象中有一个Promise是已决状态,那么返回的Promise对象就会立刻成为已决状态。

Promise.all()方法得必须等到所有传入的Promise全部变为已决状态,所返回的Promise才会已决。

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

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

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

let p4 = Promise.race([p1,p2,p3]);
p4.then(value=>{
    console.log(Array.isArray(value)); //false
    console.log(value); //1
})

Promise.race() 方法传入的Promise中哪一个Promise先变成已完成状态,就会将值传递给所返回的Promise对象的完成处理函数中。若哪一个Promise最先变成已拒绝状态,同样的,会将值传递给p4的拒绝处理函数中。

4. 继承Promise

可以继承Promise实现自定义的Promise,例如:

class MyPromise extends Promise {
    // 使用默认构造器
    success(resolve, reject) {
        return this.then(resolve, reject);
    } 
    failure(reject) {
        return this.catch(reject);
    }
} 
let promise = new MyPromise(function(resolve, reject) {
    resolve(42);
});
promise.success(function(value) {
    console.log(value); // 42
}).failure(function(value) {
    console.log(value);
});

在此例中, MyPromise 从 Promise 上派生出来,并拥有两个附加方法。 success() 方法模拟了 resolve()failure() 方法则模拟了 reject()

5. 总结

  1. Promise 具有三种状态:挂起、已完成、已拒绝。一个 Promise 起始于挂起态,并在成功时转为完成态,或在失败时转为拒绝态。 then() 方法允许你绑定完成处理函数与拒绝处理函数,而 catch() 方法则只允许你绑定拒绝处理函数;

  2. 能够将多个Promise串联起来组成Promise链,并且能够在中间传递值,甚至是传递Promise对象。 then() 的调用都创建并返回了一个新的 Promise ,只有在前一个 Promise 被决议过,新 Promise 也会被决议。 同时也可以使用Promise.all()和Promise.race()方法来管理多个Promise。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容