Promise:一种更优的异步编程统一方案 学习笔记

文章内容输出来源:拉勾大前端高薪训练营

1、Promise 概述 和 基本用法

为了避免回调地狱的问题(大量回调函数的嵌套),commonJS社区提出了Promise的规范,目的为异步编程题提供一种更合理,更强大的统一解决方案,在ES2015中被标准化,成为语言规范

定义
promise 就是一个对象,表示一个异步任务最终结束过后是成功还是失败,就像是内部对外界做的一个承诺,明确结果之后不能更改。

注意
即便 promise 中没有任何的异步操作,then方法中指定的回调函数,仍然会进入到回调队列中排队,也就是必须要等到同步代码执行完了,这里才会执行。

const promise = new Promise(function( resolve, reject){
  //这里兑现“承诺”
  resolve(100);  //承诺达成
  //reject(new Error('promise rejected'));  //承诺失败
});
promise.then(function(value){
  console.log(value);
}, function(value){
  console.log(value);
})
console.log('end');
//执行结果:end、100

2、Promise 常见误区

嵌套使用的方式是使用 Promise 最常见的错误

要借助于 Promise 对象中 then 方法链式调用的特点,尽可能的保证异步任务扁平化

3、Promise 链式调用

then 方法的作用就是为 Promise 对象添加状态明确后的回调函数

  • 它的第一个参数是 onFulfilled 回调,也就是成功之后的回调;
  • 它的第二个参数是 onRejected 回调,也就是失败之后的回调,可以省略
  • 最大的特点是内部也会返回一个 Promise 对象。

目的是实现一个 promise 的链条,也就是一个承诺结束之后,再返回一个新的承诺,每个承诺都可以负责一个异步任务,相互之间没有什么影响。

特点
1、Promise 对象的 then 方法会返回一个全新的Promise 对象
2、后面 then 方法就是在为 上一个 then 返回的 Promise 注册回调
3、前面 then 方法中回调函数的返回值 会作为后面 then 方法回调的参数
4、如果回调中返回的是Promise, 那后面的 then 方法的回调会等待它的结束

每一个 then方法 它都是为上一个 then 返回的 promise 对象添加 状态明确之后的回调,这些promise 依次执行。

  • 也可以 在 then 中 手动返回一个 promise 对象,那么下一个then 就是为这个对象添加 状态明确之后的回调
  • 如果 回调 返回的是一个普通的值,这个值 就会 当成 当前这个then 方法 返回的promise 中的值,那么下一个then 方法中接收到的回调的参数就是这个值。
  • 如果 回调中没有返回值 任何值,默认就会返回 undefined。

4、Promise 异常处理

  • 如果promise 执行中出现异常或者失败,then 中的 onRejected 都会执行;
  • promise中的异常会往后传递,catch 方法可以捕获链式中所有的异常
  • then 中只能捕获到上一个 promise 的异常

promise 的 catch 方法注册onRejected 回调,相当于 then 方法的别名,第一个参数是 undefined;

对于链式调用,建议使用分开捕获异常回调,因为promise链式回调中 异常会向后传递。

//Promise 方式的ajax
function ajax(url){
  return new Promise(function(resolve, reject){
    var xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    xhr.responseType = 'json';
    xhr.onload = function (){  //请求完成 也就是 readyStatus = 4 才会执行
      if(this.status == 200){
        resolve(this.response);
      }else{
        reject(new Error(this.statusText));
      }
    }
    xhr.send();
  })
}
ajax('/api/users.json').then(function(res){
  console.log(res);
  return ajax('/api/urls.json')
},function(error){
  console.log(error);
})
.then(function(value){
  console.log(111);
  console.log(value);
  // return ajax('/kdkdk')
})
.then(function(value){
  console.log(2222);
  console.log(value);
  return 'foo';
})
.then(function(value){
  console.log(444);
  console.log(value);
})
.catch(function(error){
  console.log(error);
});

执行结果


WX20210105-185421@2x.png

五、Promise 静态方法

1、Promise.resolve() :快速把一个值转化成一个 Promise 对象

  • 这个方法 如果接收到的是另一个promise 对象,这个 promise 对象会被原样返回;
  • 如果这个方法接收一个有 then 方法相同的 方法 的 对象,也可以返回promise 对象
Promise.resolve('200')
  .then(function(value){
    console.log(value)
  })
//等价于
new Promise(function(resolve, reject){
  resolve('200')
}).then(function(value){
  console.log(value);
});
//结果: 200 200

var promise1 = ajax('/api/users.json');
var promise2 = Promise.resolve(promise1);
console.log(promise1 === promise2); 
//结果: true

Promise.resolve({
  then: function(onFulFiled, onRejected){
    onFulFiled('foo');
  }
})
.then(function(value){
  console.log(value);
})
//结果: foo

2、Promise.reject(): 快速创建一个失败的Promise 对象

Promise.reject('anything')
  .catch(function(error){
    console.log(error)
  })
//结果:anything

六、Promise 并行执行

Promise.all() 和 Promise.race() 方法

1、Promise.all(): 可以将多个promise 合并成一个 Promise 统一去管理,返回的 value 是一个数组

  • 它接收 一个数组,数组由一个一个的promise 对象组成,可以看成是一个一个 的任务,
  • 返回一个全新的 Promise 对象,数组中所有的 Promise 任务都结束了之后,这个 Promise 任务才完成,只要数组中有一个任务失败,它就也失败
  • 成功返回的结果是一个数组,包含每个异步任务执行过后的结果。
Promise.all([
  ajax('/api/users.json'),
  ajax('/api/urls.json')
]).then(function(values){
  console.log(values)
})
//综合使用串联和并行执行 的这两种方式
ajax('/api/urls.json')
.then(value => {
  const urls = Object.values(value);
  const tasks = urls.map( url => ajax(url))
  return Promise.all(tasks)
}).then( values =>{
  console.log(values)
})

执行结果


WX20210105-185421@2x.png

2、Promise.race(): 可以将多个promise 合并成一个 全新的 Promise 对象,只会等待第一个结束的任务

  • 它跟着第一个结束的任务,一起结束,只要有任何一个promise 异步任务完成了,它就也完成
const request = ajax('/api/users.json');
const timeout = new Promise( (resolve, reject) => {
    setTimeout(() => reject(new Error('timeout')), 500)
  });
Promise.race([
  request,
  timeout
])
.then(value => {
  console.log(value)
})
.catch( error => {
  console.log(error);
}) 

通过修改网络限速的方式查看到效果:


WX20210105-185421@2x.png

七、Promise 执行时序 / 宏任务vs微任务

promise 要等到同步代码执行完了,promise 回调 才会执行

宏任务 和 微任务

  • 回调队列中的任务称之为 “宏任务”,宏任务执行过程中可以临时加上一些额外的需求
    -- 可以选择作为一个新的宏任务进到队列中 进行排队,
    -- 也可以作为当前任务的微任务,直接在当前的任务结束之后立即执行
  • Promise 的回调会作为微任务执行
  • setTimeout 回调会作为宏任务进入到 宏任务进到队列的末尾
console.log('global start');
setTimeout(() => {
 console.log('setTimeout');
}, 0)
Promise.resolve()
 .then(() => {
   console.log('promise1')
 })
 .then(() => {
   console.log('promise2')
 })

console.log('global end');
// 执行结果: global start、global end、promise1、promise2、setTimeout

微任务的好处:

  • 减少操作时用户感知到的延迟
  • 确保任务顺序的一致性,即便当结果或数据时同步可用的
  • 批量操作的优化

微任务可以提高整体的响应能力
目前绝大多数异步调用都是作为宏任务执行
Promise对象、MutationObserver对象、queueMicrotask(微任务队列) 和 node 中的 process.nextTick 他们都是作为微任务,直接在本轮任务的末尾就执行了

//创建微任务
 queueMicrotask(() => {
     console.log('微任务')
 })

批量操作优化:下面的微任务(queueMicrotask中的回调)只执行了一次就可以发送三条消息

const msgQueue = [];
function sendMessage(msg){
    msgQueue.push(msg);
    if(msgQueue.length == 1){
        queueMicrotask(() => {
            const json = JSON.stringify(msgQueue);
            msgQueue.length = 0;
            // fetch('url-of-receiver', json)
            console.log(json)
        })
    }
}
sendMessage('哈哈哈');
sendMessage('你好呀');
sendMessage('呵呵哒');

八、Promise 源码相关的说明

1、Promise 就是一个类,在执行这个类的时候,需要传递一个执行器进去,执行器会立即执行
2、Promise 中有三种状态,分别为: 成功(fulfilled)、失败(rejected)、等待(pending)
状态改变:pending -> fulfilled 或者 pending-> rejected, 一旦状态确定就不可更改
3、resolve 和 reject 函数是用来更改状态的:resolve: fulfilled、reject: rejected
4、then 方法的内部做的事情就是判断状态:如果状态是成功,调用成功的回调函数,如果是状态失败,调用失败的回调函数。then 方法是被定义在原型对象中的
5、then成功回调有一个参数,表示成功之后的值,失败回调有一个参数,表示失败之后的原因
6、then方法可以不传递参数,这个Promise 的状态会依次向后传递,直到传递给有回调函数的 then 方法。
7、resolve 把给定的值转化成 promise 对象
8、all 方法: 解决异步并发问题。按照异步代码调用的顺序得到异步代码执行的结果;

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

推荐阅读更多精彩内容