Javascript学习笔记-Promise

Javascript中Promise.png

1. Promise简介

Promise是ES6新引入的对象,是新增加的异步处理手段。
在Javascript 中在异步后要进行操作,最常用的手段是回调,例如:

ajax({success: function(data){
  // success logic
}, fail: function(err) {
  // error logic
}})

但是回调存在一些被大家所熟知的问题,类似回调地狱(回调嵌套),而且回调存在的一个很严重的问题,当我们把回调作为方法传递给异步方法以后,我们拿不到任何于这个异步回调相关的句柄,也就是说,我们只能等待回调通过事件循环机制去调用,拿不到和回调相关的任何信息,将回调的控制权转移给了异步方法。
Promise和回调的区别在于Promise在创建以后会返回一个句柄,而我们通过这个句柄,就可以对异步结果进行处理,主动权回到了我们手上。
举个书上简单的例子:我们去麦当劳吃饭(麦当劳绝对没给我赞助。。。),去点单,然后付款,如果采用回调的方式,付款后就什么都不给你,你需要一直等着叫你,叫到你了马上去拿食物,而采用Promise的方式,付款后会给你一个小票,只要食物准备好了,你拿着这个小票可以随时的来获取食物。
也就是说,对于回调来说,回调方法本身就是异步调用结果,而对于Promise来说,对于异步调用结果是类似于事件注册和事件监听(小票)。

2. Promise的创建

2.1 new Promise()

使用new Promise()创建Promise需要传递一个有两个参数的函数

new Promise((resolve, reject) => {
  setTimeout(_=>{resolve(1)}, 1000);
})

这里有几点需要注意的内容:

  1. Promise中的函数会立即执行
  2. resolvereject将产生决议值,只会决议一次,产生一个决议值,且决议值在决议后不会发生变化
  3. resolve会尝试展开参数的值,展开按照以下规则:
    3.1 如果参数是Promise类型或者thenable(下面会说明)类型,那么会尝试进行展开该参数,直到拿到最后的决议值
    3.2 如果参数是非上述情况,则直接将该值作为决议值
  4. reject会将参数值作为决议值直接返回

分别做一下说明:
Promise中的函数会立即执行,也就是说,Promise函数本身不是异步的,但是其中可以包含异步过程

console.log(1);
new Promise((resolve, reject)=> {
  console.log(2);
  setTimeout(_=>{console.log(4);},1000)
})
console.log(3);
// 输出结果是 1,2,3,4

决议值,是异步方法后返回的结果,一个Promise只能产生一个决议值,而且只能决议一次,一旦决议就会永远保持这个状态,成为不变值。也就是通过resolve或者reject方法处理后的结果,只会将第一次的结果作为决议值

// 只能决议一次
var p1 = new Promise((resolve, reject) => {
  resolve(1);
  resolve(2);
})
p1.then(fulfill => {
  console.log(fulfill); // 1
});
// 决议后值不变, 可以多次调用
p1.then(fulfill => {
  console.log(fulfill); // 1
});
p1.then(fulfill => {
  console.log(fulfill); // 1
})

thenable类型是指含有then方法的对象,包括原型链上还有then方法的对象(then方法可以有两个参数,分别对应了resolvereject

var obj = {
  then(cb, ecb) {
     cb(1);
  }
}

resolve展开可以通过下面的例子理解

var obj = {
  then(cb, ecb) {
     cb(1);
  }
}
var p1 = new Promise((resolve, reject) => {
  resolve(1)
})
new Promise((resolve, reject) => {
  resolve(obj); // 或者 resolve(p1)
}).then(fulfill => {
  console.log(fulfill); // 1
})

reject不进行展开可以通过下面的例子理解

var obj = {
  then(cb, ecb) {
     cb(1);
  }
}
var p1 = new Promise((resolve, reject) => {
  resolve(1)
})
new Promise((resolve, reject) => {
  reject(obj); // 或者 reject(p1)
}).then(null, reject => {
  console.log(reject === obj); // true,或者console.log(reject === p1)
})

2.2 Promise.resolve()

Promise.resolve()会接收一个对象,并返回一个决议的Promise,根据接收对象不同,返回的Promise的决议值会有不同操作

  1. 如果接受对象是Promise类型,那么将直接返回该对象
  2. 如果接受对象是thenable类型,那么会尝试展开,将最后的决议值作为返回Promise的决议值
  3. 非以上两种情况,将该值作为返回Promise的决议值

这里有两个地方需要注意

  • Promise类型作为参数和之前resolve的处理方式不同
  • 这里的Promise.resolve产生的Promise决议值不一定就是成功的,也有可能是失败的

用例子是最好说明的:

var obj = {
  then(cb, ecb) {
     cb(1);
  }
}
var p1 = new Promise((resolve, reject) => {
  resolve(1)
})
// Promise对象直接返回
console.log(p1 === Promise.resolve(p1)); // true
// 展开thenable
Promise.resolve(obj).then(fulfill => {
  console.log(fulfill); // 1
})
// 直接作为决议值
Promise.resolve(obj).then(fulfill => {
  console.log(fulfill); // 1
})
// 返回的决议值为失败
Promise.resolve(new Promise((resolve, reject)=>{
  reject(1)
})).then(fulfill => {
  // 不会运行到这里
  console.log('fulfill');
  console.log(fulfill);
}, reject => {
  // 输出结果
  console.log('reject');
  console.log(reject);
})

2.3 Promise.reject()

Promise.reject()也是接收一个参数,会产生一个决议值一定为rejectPromise,且和reject一样,将参数作为返回Promise的决议值

var obj = {
  then(cb, ecb) {
     cb(1);
  }
}
var p1 = new Promise((resolve, reject) => {
  resolve(1)
})
console.log(p1 === Promise.reject(p1)); // false,因为相当于返回Promise(p1)
// 展开thenable
Promise.reject(obj).then(fulfill => {
  // 不会运行到这里
}, reject => {
  console.log(obj === reject); // true
})

3. Promise的使用

在获取到Promise以后,调用then方法可以对决议值进行操作,then方法调用的时候包含两个参数,分别接收成功和拒绝时候的决议值,和我们经常将回调拆分为成功回调合失败回调的做法类似。

var p1 = new Promise((resolve ,reject) => {
  resolve(1);
});
p1.then(fulfill => {
  // 如果决议成功将调用
  console.log('fulfill');
  console.log(fulfill); // 1
}, reject => {
  // 如果决议失败将调用
  console.log('fulfill');
  console.log(reject);
})

4. Promise的模式

Promise模式是为了处理多个异步事件之间的关系

4.1 Promise.all()

Promise.all()接受一个数组作为参数,他会等待所有的Promise都完成以后,返回一个决议值,这个决议值是根据传递参数数组顺序,将每一个Promise的决议值放倒对应的位置,如果数组中任何一个Promise拒绝,则会将该拒绝值作为结果,如果是空数组,则会立即进行决议

var obj = {
  then(cb, ecb) {
    setTimeout(_=> cb(2), 100);
  }
}
var p1 = new Promise((resolve, reject) => {
  setTimeout(_=> resolve(3), 100);
})
Promise.all([1, obj, p1]).then(fulfill => {
 // 可以利用解构来获取到对应的值 var [v1,v2,v3] = fulfill
  console.log(fulfill.length === 3); // true
  console.log(fulfill[0] === 1); // true
  console.log(fulfill[1] === 2); // true
  console.log(fulfill[2] === 3); // true
})
// 有任何拒绝,则拒绝值作为结果
Promise.all([1, obj, p1, Promise.reject(4)]).then(fulfill => {
}, reject => {
  console.log(reject === 4); // true
})
// 空数组立即决议
Promise.all([]).then(fulfill => {
  console.log('fulfill');
})

4.2 Promise.race()

Promise.race()Promise.all()类似,同样接受一个数组作为参数,但是区别在于Promise.race()只会返回最快进行决议的结果作为结果值,且如果为空数组则永远不会进行决议(相当于永远在等待最快的决议的结果,但是空数组一直没有决议,所以会一直等待)

var p1 = new Promise((resolve, reject) => {
  setTimeout(_=> resolve(1), 100);
})
var p2 = new Promise((resolve, reject) => {
  setTimeout(_=> resolve(2), 150);
})
var p3 = new Promise((resolve, reject) => {
  setTimeout(_=> reject(3), 50);
})
Promise.race([p1, p2]).then(fulfill => {
  console.log('fulfill');
  console.log(fulfill === 1); // true, 因为p1会比p2先进行决议
})
Promise.race([p1, p3]).then(fulfill => {
}, reject => {
  console.log('reject');
  console.log(reject === 3); // true, 因为p3会比p1先进行决议,且决议拒绝
})
// 空数组永远不会决议
Promise.race([]).then(fulfill => {
  console.log('fulfill'); // 永远不会执行
}, reject => {
  console.log('reject'); // 永远不会执行
})

5. Promise的特点

5.1 Promise链

Promise在执行then方法以后,会返回一个Promise对象,这个新的Promise的决议值是根据上一个then方法中的return值来确定的,于是形成了一个Promise的链路

var p1 = new Promise((resolve, reject) => {
  setTimeout(_=> resolve(1), 100);
});
var p2 = p1.then(fulfill => {
  console.log(fulfill); // 1
  return 2; // 返回的值会作为链路上新的Promise的决议值
});
// 其实p2相当于
var p2 = new Promise((resolve, reject) => {
  resolve(2);
});

5.2 Promise异常处理

Promise和回调一样,因为决议过程是异步,所以没办法使用try catch来捕获异常,Promise链路上如果产生异常,那么会认为Promise决议被拒绝,该异常会作为拒绝的决议值,放倒链路上下一个Promise中。

var err = new Error(1);
var p1 = new Promise((resolve, reject) => {
  throw err;
  setTimeout(_=> resolve(1), 100);
});
var p2 = p1.then(fulfill => {
    console.log('fulfill'); // 不会执行到这里 
}, reject => {
  console.log(reject === err); // true
});

也就是说p1产生的异常,自身并不能进行处理,必须要使用链路上下一个Promise也就是p2中处理,如果不进行处理,那么该错误就会被丢掉了
同时,对于异常处理可以使用catch方法,接收一个参数,相当于then(null, reject)

var err = new Error(1);
var p1 = new Promise((resolve, reject) => {
  throw err;
  setTimeout(_=> resolve(1), 100);
});
var p2 = p1.catch(reject => {
  console.log(reject === err); // true
});

6. 总结

Promise比起回调来说,可以减少回调地狱带来的代码风格问题,也将控制权重新回到了我们手中。

7. 参考

《你不知道的Javascript(中篇)》
MDN-Promise

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

推荐阅读更多精彩内容