Promise
主要API和属性
Promise 构造函数: Promise (excutor) {}
- 同步函数,是个执行器,会立即执行
Promise.prototype.then 方法: (onResolved, onRejected) => {}
- 根据状态去执行不一样的回调函数
Promise.prototype.catch 方法: (onRejected) => {}
- Promise.prototype.catch 用来捕获 promise 的异常,就相当于一个没有成功的 then。
Promise.resolve 方法: (value) => {}
- 默认产生一个成功的 promise。
Promise.reject 方法: (reason) => {}
- 默认产生一个失败的 promise,Promise.reject 是直接将值变成错误结果
Promise.all 方法: (promises) => {}
- promise.all 是解决并发问题的,多个异步并发获取最终的结果(如果有一个失败则失败)。
Promise.race 方法: (promises) => {}
- Promise.race 用来处理多个请求,采用最快的(谁先完成用谁的)
属性
-
PromiseStatus
- pending
- resolved
- rejected
-
PromisResult
关键问题
Promise从两眼发懵到双眼放光(3)-Promise的几个关键问题(一)
Promise从两眼发懵到双眼放光(4)-Promise的几个关键问题(二)
如何改变 promise 的状态?
- 当执行resolve(value)时,状态会从pending变为resolved
当执行reject(reason)或者抛出Error时,状态会从pending变为rejected
一个 promise 指定多个成功/失败回调函数, 都会调用吗?
- 都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再指定回调
- 如何先改状态再指定回调?
1). 在executor中直接调用resolve()/reject()
2). 延迟更长时间才调用then() - 什么时候才能得到数据?
1). 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
2). 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据
改变 promise 状态和指定回调函数谁先谁后?
- 改变promise状态先因为回调函数是异步的
promise.then()返回的新 promise 的结果状态由什么决定?
-
- 简单表达: 由then()指定的回调函数执行的结果决定
- 详细表达:
1). 如果抛出异常, 新promise变为rejected, reason为抛出的异常
2). 如果返回的是非promise的任意值, 新promise变为resolved, value为返回的值
3). 如果返回的是另一个新promise, 此promise的结果就会成为新promise的结果
promise 如何串连多个操作任务?
- promise的then()返回一个新的promise, 通过then的链式调用串连多个同步/异步任务
promise 异常传透?
-
- 当使用promise的then链式调用时, 可以在最后指定失败的回调,
- 前面任何操作出了异常, 都会传到最后失败的回调中处理
中断 promise 链?
-
通过抛出一个异常来终止
-
通过reject来中断
-
在回调函数中返回一个pending状态的promise对象
-
new Promise((resolve, reject) => { resolve(1) }).then( value => { console.log('onResolved1()', value) return new Promise(() => {}) // 返回一个pending的promise 中断promise链 }, ).then( //不会进入这个回调 value => { console.log('onResolved2()', value) }, reason => { console.log('onResolved2()', reason) }, )
-
同步迭代器接收异步任务的时候怎么办?
- 利用观察者模式,因为是异步,状态还是pending,所以先进行订阅者订阅,被动将异步函数push进消息队列,在reject,resolved进行发布,也就是将消息队列进行遍历,实现异步任务调用
基本特征
-
promise 有三个状态:pending,fulfilled,or rejected;「规范 Promise/A+ 2.1」
-
new promise时, 需要传递一个executor()执行器,执行器立即执行
-
executor接受两个参数,分别是resolve和reject
-
try{ //同步调用『执行器函数』 executor(resolve, reject); }catch(e){ //修改 promise 对象状态为『失败』 reject(e); }
- promise 的默认状态是 pending;
- this.PromiseState = ‘pending’;
-
promise 有一个value保存成功状态的值,可以是undefined/thenable/promise;「规范 Promise/A+ 1.3」
-
promise 有一个reason保存失败状态的值;「规范 Promise/A+ 1.5
-
promise 只能从pending到rejected, 或者从pending到fulfilled,状态一旦确认,就不会再改变
-
promise 必须有一个then方法,then 接收两个参数,分别是 promise 成功的回调 onFulfilled, 和 promise 失败的回调 onRejected;「规范 Promise/A+ 2.2」
-
如果调用 then 时,promise 已经成功,则执行onFulfilled,参数是promise的value
-
如果调用 then 时,promise 已经失败,那么执行onRejected, 参数是promise的reason
-
如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then 的失败的回调onRejected;
-
异步操作——利用观察者模式
-
因为 promise 调用 then 方法时,当前的 promise 并没有成功,一直处于 pending 状态。所以如果当调用 then 方法时,当前状态是 pending,我们需要先将成功和失败的回调分别存放起来,在executor()的异步任务被执行时,触发 resolve 或 reject,依次调用成功或失败的回调
-
if(this.PromiseState === 'pending'){ //保存回调函数 this.callbacks.push({ onResolved: function(){ callback(onResolved); }, onRejected: function(){ callback(onRejected); } }); }
-
- then 的链式调用
- promise 的优势在于可以链式调用。在我们使用 Promise 的时候,当 then 函数中 return 了一个值,不管是什么值,我们都能在下一个 then 中获取到,这就是所谓的then 的链式调用。
- 值穿透特性
-
而且,当我们不在 then 中放入参数,例:promise.then().then(),那么其后面的 then 依旧可以得到之前 then 返回的值,这就是所谓的值的穿透。
-
then 的参数 onFulfilled 和 onRejected 可以缺省,如果 onFulfilled 或者 onRejected不是函数,将其忽略,且依旧可以在下面的 then 中获取到之前返回的值;
-
//判断回调函数参数 if(typeof onRejected !== 'function'){ onRejected = reason => { throw reason; } } if(typeof onResolved !== 'function'){ onResolved = value => value; //value => { return value}; }
-
promise 可以 then 多次,每次执行完 promise.then 方法后返回的都是一个“新的promise";「规范 Promise/A+ 2.2.7」
-
如果 then 的返回值 x 是一个普通值,那么就会把这个结果作为参数,传递给下一个 then 的成功的回调中;
-
如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then 的失败的回调中;「规范 Promise/A+ 2.2.7.2」
-
如果 then 的返回值 x 是一个 promise,那么会等这个 promise 执行完,promise 如果成功,就走下一个 then 的成功;如果失败,就走下一个 then 的失败;如果抛出异常,就走下一个 then 的失败;「规范 Promise/A+ 2.2.7.3、2.2.7.4」
-
//封装函数
function callback(type){
try{
//获取回调函数的执行结果let result = type(self.PromiseResult); console.log("result---"+result) //判断 if(result instanceof Promise){ //如果是 Promise 类型的对象 result.then(v => { console.log('sus') resolve(v); }, r=>{ console.log('err') reject(r); }) }else{ //结果的对象状态为『成功』 resolve(result); } }catch(e){ reject(e); } }
-
if(typeof onRejected !== 'function'){ onRejected = reason => { throw reason; } }
9k字 | Promise/async/Generator实现原理解析
Generator
什么是Generator 如何定义
Generator 是一个带星号的“函数”(它并不是真正的函数,下面的代码会为你验证),可以配合 yield 关键字来暂停或者执行函数。
function* gen() {
console.log("enter");
let a = yield 1;
let b = yield (function () {return 2})();
return 3;
}
var g = gen() // 阻塞住,不会执行任何语句
console.log(g.next())
console.log(g.next())
console.log(g.next())
console.log(g.next())
// output:
// { value: 1, done: false }
// { value: 2, done: false }
// { value: 3, done: true }
// { value: undefined, done: true }
是怎么工作的
- 生成器不一定会执行生成器函数体,通过创建迭代器对象,可以与生成器通信
- 迭代器用于控制生成器的执行。迭代对象暴露的最基本接口是next方法迭,这方法可以用来向生成器请求一个值, 从而控制生成器
- next函数调用后,生成器就开始执行代码,当代码执行到yield关键字时,就会生成一个中间结果(生成值序列中的一项),然后返回一一个**新对象,**其电封装了结果健一个指示完成的指示器。
- 每当生成一个当前值后,生成器就会非阻塞地挂起执行,随后耐心等待下一次值滑求的到达。这是普通函数完全不具有的强大特性,后续的例子中它还会起到更大的作用。
返回值
Promise对象又分两种方式
- return new Promise((resolve,reject){resolve()})
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GGtLPZVu-1616849620185)(/Users/macos/Documents/photo/image-20210324211252242.png)]
- async function(){ await 异步函数/对象}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KAfw7ur3-1616849620187)(/Users/macos/Documents/photo/image-20210324212033719.png)]
对于非promise对象
我们可以直接返回,也可以通过next(…),并在本质上要求调用代码为 yield表达式提供一个结果值。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZbON7iMH-1616849620188)(/Users/macos/Documents/photo/截屏2021-03-24 下午8.57.21.png)]
Generator的内部构成
我们已经知道了调用一个生 成器不会实际执行它。相反,它创建了一一个新的迭代器,通过该迭代器我们才能从生成器中请求值在生成器生成(或让渡)了一个值后,生成器会挂起执行并等待下一一个请求的到来。在某种方面来说,生成器的工作更像是一个小 程序,一个在状态中运 动的状态机。
-
挂起开始创建了一个生成器后,它最先以这种状态开始。其中的任何代码都未执行。
-
执行生成器中的代码执行的状态。执行要么是刚开始,要么是从上次挂起的时候继续的。当生成器对应的迭代器调用了next方法,并且当前存在可执行的代码时,生成器都会转移到这个状态,
-
挂起让渡——当生成器在执行过程中遇到了一个yield表达式,它会创建一个包含着返回值的新对象,随后再挂起执行。生成器在这个状态暂停并等待继续执行。
-
完成——在生成器执行期间,如果代码执行到return 语句或者全部代码执行完毕,生成器就进入该状态。
与生成器的交互
- 作为生成器函数参数发送值
- 使用next方法向生成器发送值
yield基本介绍
yield 同样也是 ES6 的新关键词,配合 Generator 执行以及暂停。yield 关键词最后返回一个迭代器对象,该对象有 value 和 done 两个属性,其中 done 属性代表返回值以及是否完成。yield 配合着 Generator,再同时使用 next 方法,可以主动控制 Generator 执行进度。
Generator的工作机制是什么呢?因为在js里面是单线程,会先生成一个生成器,执行权一开始在生成器,遇到yield关键字,生成器会让步,状态变成暂时挂起,执行权交于迭代器,迭代器通过next()通知生成器恢复执行权,这时候要想的是返回什么?这里有两种返回结果,一种是promise对象,一种是非promise对象。
对于promise对象,我们通过回调函数,将返回结果进行打印,同时可以控制执行器的发起,这里就涉及成功回调和错误抛出
Async/await
Async/await的工作原理
- 一句话,async 函数就是 Generator 函数的语法糖
- 一比较就会发现,async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await,仅此而已。
- async 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里。
- await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try…catch 代码块中
异步生成器
生成器可以 yield 一个 promise,然后这个 promise 可以被绑定,用其完成值来恢复这个生成器的运行。
异步,生成器,迭代器如何运行?这个魔法如何实现?
我们需要一个可以运行生成器的运行器(runner),接受一个yield 出来的 promise,然后将其连接起来用以恢复生成器,方法是或者用完成成功值,或者用拒绝原因值抛出一个错误到生成器。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HOpWj8oI-1616849620190)(/Users/macos/Documents/photo/image-20210325095328275.png)]
Promise + Generator
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oWpQQKiH-1616849620191)(/Users/macos/Documents/photo/image-20210324214105035.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sYobosXJ-1616849620191)(/Users/macos/Documents/photo/image-20210324214126727.png)]
Async/await+Generator
首先要明白async/await的工作机制
async会返回一个promise对象,而这个await的作用就是返回成功的回调函数,所以这里不需要then。这里也很很好地说明了有async必有await。
在Async/await+Generator的生成器对象也就是迭代器是一个异步生成器对象,也就是说和一般的生成器不同,所以一般用for…of不能将生成器进行迭代,这里有两种方法,一种是手写迭代器,一种asnyc➕for await …of进行迭代
手写迭代器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pxvUgcQ7-1616849620192)(/Users/macos/Documents/photo/image-20210325091255540.png)]
因为一般的生成器对象用next()方法可以返回一个带有value和done属性的对象,我们可以根据这个done去判断是否继续迭代。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gtrNh9Kq-1616849620193)(/Users/macos/Documents/photo/image-20210325091147060.png)]
asnyc➕for await …of
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rylQ9PsO-1616849620193)(/Users/macos/Documents/photo/image-20210325091446035.png)]
观察者模式
订阅/注册(resigter)则为被动->订阅者,发布(fire)则为主动->观察者
这里需要注意的是要先订阅,将消息类型先push进去,然后发布者再进行遍历
观察者类
// <h2>订阅(注册)则是被动,发布则是主动</h2>
//使用立即函数
var Observe =(function(){
var message = {}
return {
//注册订阅信息模块
//注册就是订阅者去注册信息!!这是被动的
register:function(type,fn){
//检测有没有这个类型的消息队列
//不存在就创建一个该消息类型
if (typeof message[type] === 'undefined'){
message[type] = [fn]
}else{
message[type].push(fn)
}
},
//发布订阅信息模块
//发布就是观察者去发布信息
fire:function(type,args){
//判断是否有,就遍历,没有就返回
if (!message[type]) return
//要发布消息就要有发布信息对象,其中包括消息类型和携带消息
events = {
type:type,
args:args
}
//这里是指消息对应的动作序列的长度,不是message.length
for(let i = 0;i<message[type].length;i++){
message[type][i].call(this,events)
}
},
//取消订阅信息模块
remove:function(type,fn){
//需要判断消息队列和动作
//判断是否是消息队列,也就是数组,好比二维数组
if(message[type] instanceof Array){
//从最后一个消息进行遍历
for(let i = message[type].length;i>=0;i--){
//如果存在该动作!!!!
if(message[type][i] == fn){
//splice,从i动作开始,删除一个
console.log('4')
message[type][i].splice(i,1)
}
}
}
}
}
})()
// //订阅消息->被动触发
// Observe.register('test',function(e){
// console.log(e.args)
// })
// //发布消息->主动发布
// Observe.fire('test',{fn:'happy1'})
// Observe.fire('test',{fn:'happy2'})
// Observe.fire('test',{fn:'happy3'})
// // Observe.remove('test',function(e){
// // console.log(e.args)
// // })
// console.log(Observe)
例子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src='./观察者模式.js'></script>
</head>
<body>
<script>
console.log(Observe)
//学生类,是被动的,则是订阅/注册信息的
//教师类,是主动的,则是发布信息的
//实现了老师一个问题多个学生回答问题,学生回答的问题都给我push到这个消息队列
//根据输出结果可以看出这是利用了队列,先进先出
var Student = function(result){
var self = this
self.result = result
self.say = function(){
console.log(self.result)
}
}
Student.prototype.answer = function(question){
//进行订阅,注册
console.log('问题是'+question)
Observe.register(question,this.say)
}
var Teacher = function(){}
Teacher.prototype.ask = function(question){
//进行发布
Observe.fire(question)
}
var student1 = new Student('学生1回答了该问题')
var student2 = new Student('学生2回答了该问题')
var student3 = new Student('学生3回答了该问题')
//学生进行回答问题
student1.answer('什么是设计模式')
student2.answer('什么是观察者模式')
student3.answer('什么是建造者模式')
//老师进行提问
var teacher = new Teacher()
teacher.ask('什么是设计模式')
teacher.ask('什么是观察者模式')
teacher.ask('什么是建造者模式')
</script>
</body>
</html>