异步三部曲之Promise

开始的异步处理方式

getDataA(params, function(dataA){
      // ...
    getDataB(params, function(dataB){
        // ...
        getDatac(params, function(dataC){
          // ...
        })
    })
})

开始的异步处理方式是这种通过callback进行处理,一步步去处理,当需要处理N步时就要有N个回调函数,代码就会纵向发展,同时使阅读性大大降低。
这种方式有个名字叫:地狱回调!很好理解,就像地狱一样,一层一层的进行调用。

Promise

基本用法

// 创建一个Promise对象
const promise = new Promise(function(resolve, reject) {
  // 你可以在里面执行你的异步操作,例如请求数据
    getDataA(params, function(dataA) {
      if(dataA) {
        // 改变状态为成功并传入数据
        resolve(dataA)
      }
      // 改变状态为失败并传入错误信息
      reject(new Error(dataA))
    })
})

promise.then(function(dataA) {
  // 成功后的回调
}, function(err) {
  // 失败后的回调
})

Promise对象接收一个函数作为参数,这个函数拥有两个参数,代表成功(resolve)和代表失败(reject)的函数
这里暴躁老哥就要说了:这不还是要回调,只不过包了一层,完全是多此一举!
大佬您别急,请继续往下看~~

Promise执行

console.log('Hello')
const promise = new Promise(function(resolve, reject) {
  console.log('Hi Promise')
  setTimeout(function() {
    console.log(1)
    resolve('成功了')
  }, 1000)
  setTimeout(function() {
    console.log(2)
    reject('失败了')
  }, 2000)
})
promise.then(function(res) {
  console.log(res);
}, function(err) {
  console.log(err);
})
console.log('Bay')

// Hello
// Hi Promise
// Bay
// 1
// 成功了
// 2

可以看到promise在创建时就会调用,并且会告诉异步的操作:哎,你先处理着,完了告诉我结果,我先去忙别的了。
Promise的特点:
1.Promise创建时就会执行
2.Promise会有三个状态,处理中、成功、失败
3.即使出了结果,里面的异步操作并不会停止!
4.虽然出了结果依旧会执行完所有异步,但是以第一个结果为准
暴躁老哥一拍桌子!好家伙,拿到结果就跑,后面说啥不管了。

Promise.prototype.then

let num = 0;

function nextF(data) {
  num +=1;
  console.log(num, data)
  return data;
}

const promise = new Promise(function(resolve, reject) {
    setTimeout(function() {
      resolve('成功了')
    }, 1000)
})

promise
.then(nextF)
.then(nextF)
.then(nextF)

// 1 成功了
// 2 成功了
// 3 成功了

这里我定义了一个函数nextF,和一个变量numnextF函数每次执行会使num+1并打印后把参数return。然后我把它传给promise.then,通过打印出的结果可以看出,then返回的是一个新的promise,并且默认是Fulfilled状态,所以then可以链式调用。

Promise.prototype.catch

let num = 0;

function nextF(data) {
  num +=1;
  console.log(num, data)
  return data;
}

const promise = new Promise(function(resolve, reject) {
    setTimeout(function() {
      reject('失败了')
    }, 1000)
})

promise
.catch(nextF)
.catch(nextF)
.catch(nextF)

// 1 失败了

这里改写了一下上面的例子,把promise的状态改为Rejected,发现catch方法只执行了一次,同样连续调用不会报错。所以catch方法和then方法一样会返回一个新的promise,并且间接证明了thencatch返回的新promise都是默认成功状态。

同时catch方法还有一个作用,捕获promise抛出的错误。
暴躁老哥:你这也证明不了返回的新promise默认是Fulfilled状态呀!那您接着往下看

let num = 0;

function nextF(data) {
  num +=1;
  console.log(num, data)
  return data;
}

const promise = new Promise(function(resolve, reject) {
    setTimeout(function() {
      resolve('成功了')
    }, 1000)
})

promise
.then(nextF)
.then(function(data) {
  num +=1;
  console.log(num, data)
  throw new Error('我喜欢红色')
})
.then(nextF)
.then(nextF)
.catch(nextF)
.then(nextF)

// 1 成功了
// 2 成功了
// 3 Error: 我喜欢红色
// 4 Error: 我喜欢红色

这里在第二个then方法中抛出一个错误,结果打印了两个成功了、两个失败了。为啥两个失败了,因为catch捕获了错误并返回,而返回的是一个新的promise,而这个新的promise默认Fulfilled状态,导致最后的then方法运行了,打印出了第二个失败了。

这里也证明了一个很神奇的功能,那就是当promise抛出错误时,会跳过后面的then直到被catch捕获。promise出现Rejected时也一样。
所以reject的作用是抛出一个异常!暴躁老哥先坐下,我知道你要说什么。当promiseRejected状态时,并且后面没有进行捕获时,控制台会打印出一个Error!

Promise.prototype.finally

new Promise(function(resolve, reject) {
  resolve('成功了')
})
.then(function(res) {
  console.log(res)
  return res
})
.catch(function(err) {
  console.log(err);
  return err
})
.finally(function(data) {
  console.log('finally', data);
  return data
})
.then(function(res) {
  console.log(res)
  return res
})

// 成功了
// finally undefined
// 成功了

// 另一个例子

new Promise(function(resolve, reject) {
  throw new Error('红扑扑')
})
.then(function(res) {
  console.log(res)
  return res
})
.catch(function(err) {
  console.log(err);
  return err
})
.finally(function(data) {
  console.log('finally', data);
  return data
})
.then(function(res) {
  console.log(res)
  return res
})

// Error: 红扑扑
// finally undefined
// Error: 红扑扑

finallythencatch方法一样返回一个新的promise并且默认Fulfilled状态。但是他接收的回调函数没有任何参数,同样返回值不会影响后面的行为,并且无论什么状态都会执行。所以一般只会用来在处理promise完毕后的善后工作

Promise.resolve

const p = Promise.resolve('a')

p.then(function(data) {
  console.log(data);
})

// a

// 打印p会得到一个Promise实例
// 所以

Promise.resolve('a')
// 等价于
new Promise(function(resolve) {
  resolve('a')
})

1.参数是一个 Promise 实例

const p = Promise.resolve(
  new Promise(function(resolve, reject) {
    setTimeout(function() {
      resolve('失败了')
    })
  })
)

p.then(function(data) {
  console.log(data);
})
.catch(function(err) {
  console.log(err)
})

// 失败了

会执行这个Promise,所以原本Promise.resolve代表Fulfilled本应会执行then,但是并未走then而是直接执行了catch

2.参数是一个thenable对象
什么是thenable对象?
thenable对象就是拥有then方法对象

let thenable = {
  a: 1,
  then: function(resolve, reject) {
    console.log(this.a)
    reject(42);
  }
};

let p1 = Promise.resolve(thenable);
p1.then(function (value) {
  console.log('then', value)
})
.catch(function(err) {
  console.log('catch', err)
});

// 1
// catch 42

会把这个thenable对象转换成Promise对象并执行then方法,所以跳过了p1.then执行了p1.catch

3.不传参数

let p1 = Promise.resolve();
p1.then(function (value) {
  console.log('then', value)
})
.catch(function(err) {
  console.log('catch', err)
});

// then undefined

Promise.resolve允许不传参数,但是会直接返回一个Fulfilled状态的Promise

Promise.reject
Promise.reject的用法等同于Promise.resolve

let p1 = Promise.reject();
p1.then(function (value) {
  console.log('then', value)
})
.catch(function(err) {
  console.log('catch', err)
});

// catch undefined

区别是resolve传入的如果是一个promise对象或者thenable对象会执行,以得到的结果为准,如果不是则转换为Promise对象并执行resolve,参数就是传入的参数。而reject无论传入什么,都会返回一个rejected状态的Promise,而传入的参数就是失败或者说错误的理由

批量处理Promise对象

const p1 = new Promise(function(resolve, reject){
  setTimeout(function() {
    resolve('p1')
  }, 1000)
})
const p2 = new Promise(function(resolve, reject){
  setTimeout(function() {
    resolve('p2')
  }, 2000)
})

const p3 = new Promise(function(resolve, reject){
  setTimeout(function() {
    reject('p3')
  }, 500)
})

const p4 = new Promise(function(resolve, reject){
  setTimeout(function() {
    resolve('p4')
  }, 0)
})

这里创建p1p2p3p4四个Promise实例

Promise.all

Promise.all([p1, p2, p4])
.then(res => {
  console.log('success', res)
})
.catch(err => {
  console.log('error', err)
})
// success ["p1", "p2", "p4"]

Promise.all([p1, p3, p4])
.then(res => {
  console.log('success', res)
})
.catch(err => {
  console.log('error', err)
})

// error p3

Promise.all接受一个数组作为参数,数组的项如果是Promise对象就会直接执行,如果不是就会默认执行上面说到的Promise.resolve进行转换,并返回一个新的Promise对象,新的Promise的状态取决于传入的一系列Promise对象:
1.p1、p2、p3的状态都为Fulfilled,这个Promise对象的状态就是Fulfilled,传入then方法是一个数组对应p1、p2、p3
2.p1、p2、p3在执行中有一个出现Rejected,就会直接调用catch,错误信息即最先Rejected的信息

Promise.race

Promise.race([p1, p2, p4])
.then(res => {
  console.log('success', res)
})
.catch(err => {
  console.log('error', err)
})
// success p4

Promise.race([p1, p3])
.then(res => {
  console.log('success', res)
})
.catch(err => {
  console.log('error', err)
})

// error p3

Promise.race的用法等同于Promise.all,区别于Promise.race的状态是赛跑原则。取决于得到的第一个状态

Promise.allSettled

Promise.allSettled([p1, p2, p3, p4])
.then(res => {
  console.log('success', res)
})
.catch(err => {
  console.log('error', err)
})
/* success [
  {
    status: "fulfilled",
    value: "p1"
  },
  {
    status: "fulfilled",
    value: "p2"
  },
  {
    status: "rejected",
    value: "p3"
  },
  {
    status: "fulfilled",
    value: "p4"
  }
]
*/

Promise.allSettled的用法等同于Promise.all,区别于Promise.allSettled不会出现rejected状态,它会等所有的Promise执行完之后一起返回

Promise.any

Promise.any([p1, p2, p3, p4])
.then(res => {
  console.log('success', res)
})
.catch(err => {
  console.log('error', err)
})

// success p4

Promise.any的用法和Promise.race一致,区别于正好相反Promise.any是当参数实例全部rejected时才会是rejected,当参数实例中出现一个Fulfilled它的状态就会是fulfilled,传入then的参数就是这个参数实例的结果

Promise处理异步

最初的例子用Promise改写

new Promise(function(resolve, reject) {
  getDataA(params, function(data) {
    if(data){
      resolve(data)
    }else {
      reject(new Error(data))
    }
  })
})
.then(function(dataA) {
  return new Promise(function(resolve, reject) {
    getDataB(params, function(data) {
      if(data){
        resolve(data)
      }else {
        reject(new Error(data))
      }
    })
  })
})
.then(function(dataB) {
  return new Promise(function(resolve, reject) {
    getDataC(params, function(data) {
      if(data){
        resolve(data)
      }else {
        reject(new Error(data))
      }
    })
  })
})
// ...
.catch(function(err) {
  // ...
})

结论

1.Promise虽然未解决地狱回调,但是解决了代码纵向发展的趋势,使可读性更高
2.虽然解决了纵向发展的问题,但是在上面的例子请求数据时,最后的请求无法直接使用第一次请求的结果

参考

ECMAScript 6 入门中Promise章节

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

推荐阅读更多精彩内容