回调函数
Nodejs通过回调函数来改进异步编程模型,回调模式与事件模式类似,异步代码都会在将来的某一时刻调用。(深入理解ES6)
回调函数使用时的注意点:JS中this的指向问题
回调函数的缺点(你不知的JavaScript):
1.回调函数的嵌套导致代码易读性很差,俗称回调地狱:
ajax("http://simple1.url",
success:function(result1){
ajax("http://simple2.url",
success:function(result2){
return result1 + result2;
})
})
2.信任问题:
使用回调函数会受到控制反转的影响,把回调函数的控制权交给第三方,以下是两种情况及其应对方法:
1.传入的回调函数完全不被调用:
//解决方法
function timeoutify(fn, delay){
let intv = setTimeout(()=>{
intv = null;
fn( new Error("Timeout!");
}, delay)
return function(){
if(intv){
//函数执行到此处说明定时器还在,回调函数已经被调用了
clearTimeout(intv);
fn.apply(this, arguments)
}
}
}
2.传入的回调函数提前被调用:
//解决方法:利用定时器永远异步调用回调函数
function asyncify(fn){
let ori_fn = fn;
let intv = setTimeout(()=>{
intv = null;//important
if(fn) fn();
},0);
fn = null;
return function(){
if(intv){
//函数执行到此处说明提前调用
//这里使用apply是利用数组传入参数?
fn = ori_fn.bind.apply(ori_fn,[this].concat([].slice.call(arguments)));
}else{
//函数执行到次数说明正常调用
ori_fn.apply(this, arguments);
}
}
}
Promsise
Promise的一些名词解释
- 内部属性[[PromiseState]]被用来表示Promise的3中状态:"pending", "resolve"(决议), "reject"(拒绝)
- fulfill(完成)
Promise的出现解决了上述问题
- 回调地狱:promise支持链式调用
- 调用过早:回调函数的调用永远在决议产生之后
- 调用过晚/不被调用:一旦决议产生后会立即调用回调函数,且不会在产生下一次异步的时候回调函数仍没有被调用
Promise的特点
- Promise相当于异步操作的占位符,它不会去订阅一个事件,也不会传递一个回调函数给目标函数,而是让函数返回一个Promise对象(支持链式调用)
- new Promise对象的时候要传入一个executor函数
- 回调函数的运行总是在promise决议完成后
- 单决议:一旦决议完成就成了不变值(immutable value),回调函数也只能被调用一次(未决议时回调函数永不调用,因此回调函数的调用不是0次就是1次)
- 如何改变promise的状态?resolve: pending -> resolved; reject/抛出异常: pending -> rejected;
- 使用then方法返回的新的promise的状态由回调函数执行的结果决定;结果可分为三种情况:1):抛出异常,新promise的状态为rejected; 2)非promise的任意值,新promise状态为resolved;3)返回一个promise,新promise的状态由回调函数产生的promise决定
- 异常值传透:使用then方法链式调用时,可以只写onResolved方法,一旦产生错误可最后由catch方法捕捉到
- 如何"中断"promise链:new Promise(()->{}),新promise实例的状态一直未pending
- Promise.all方法的特点:1)一个promise rejected,返回一个rejected的promise实例;2)返回值与promise数组一一对应
手写模拟Promise
function Promise(excutor){
this.callbacks = []
this.data = null
const PENDING = "pending"
const RESOLVED = "resolved"
const REJECTED = "rejected"
this.status = PENDING
//成功的promise值用value表示
function resolve(value){
//实现单决议
if(this.status !== PENDING) return
//改变状态
this.status = RESOLVED
//保存数据
this.data = value
//调用then方法的时候,如果promise还处于pending状态就把回调函数放入cb数组中
if(this.callbacks.length > 0){
this.callbacks.forEach(obj=>{
setTimeout(()=>{
obj.onResolved(value)
})
})
}
}
//失败的promise值用reason表示
function reject(reason){
if(this.status !== PENDING) return
this.status = REJECTED
this.data = reason
if(this.callbacks.length > 0){
this.callbacks.forEach(obj=>{
setTimeout(()=>{
obj.onRejected(value)
})
})
}
}
//注意promise的特点,抛出错误也会改变状态
try{
excutor(resolve, reject)
}catch(error){
reject(error)
}
}
Promise.prototype.then = function(onResolved, onRejected){
//给onResolved和onRejected函数指定默认值
onResolved = typeof onResolved == 'function' ? onResolved : value => value
onResolved = typeof onRejected == 'function' ? onResolved : reason => { throw reason }
//提取重复代码:根据上述promise特点6提取
const handle = callback =>{
try{
const result = callback(this.data)
if(result instanceof Promise){
//当回调函数产生的值为promise对象时
//利用then方法产生决议,此时result也可能处于pending/resolved/rejected状态
result.then(resolve, reject)
}else{
//非promise的普通值,为resolved状态
resolve(result)
}
}catch(error){
//抛出错误为rejected状态
reject(error)
}
}
//每次都要返回一个promise实例以实现链式调用
return new Promise((resolve, reject)=>{
//箭头函数不用担心this指向问题
if(this.status === "pending"){
//pending状态下将回调函数保存到this.callbacks数组中,等到决议产生了再调用回调函数
//注意:这里一定要**调用**resolve和reject函数,否则新的promise实例会一直pending
this.callbacks.push({
//此处的onResolved和handle(onResolved)中的onResolved不同
onResolved(){
handle(onResolved)
},
onRejected(){
handle(onRejected)
}
})
}else if(this.status === "resolved"){
//利用setTimeout模拟,保证回调函数永远是异步调用的
setTimeout(()=>{
handle(onResolved)
})
}else{
setTimeout(()=>{
handle(onRejected)
})
}
})
}
//catch方法返回的仍然是一个promise实例,promise不会在此中断
Promise.prototype.catch = function(onRejected){
return this.then(null, onRejected)//null占位
}
上述就是Promise最关键的两个函数,理解Promise的特点对实现Promise很重要。 接下里,继续实现剩余的方法。
Promise.resolve = function(value){
return new Promise ((resolve, reject)) =>{
//这里也要考虑非promise值
if(value instanceof Promise){
//这里也是一样要调用resolve, reject以保证返回的新promise实例一定会是fulfill的
value.then(resolve, reject)
}else{
resolve(value)
}
})
}
Promise.reject = function(reason){
return new Promise((resolve, reason) =>{
reject(reason)
})
}
Promise.all = function(promiseArr){
//计数器
let resolvedCount = 0
//创建和promiseArr相同长度的数组,以保证异步全部完成
let result = new Array(promiseArr.length)
return new Promise((resolve, reject)=>{
promiseArr.forEach((promise, index)=>{
//这里还要考虑数组中的值非promise对象
Promise.resolve(promise).then(
value => {
//异步完成后计数器才+1
resolvedCount ++
//利用index保证返回值与promise数组中元素的顺序一致
result[index] = value,
//异步全部完成
if(resolvedCount === promiseArr.length){
resolve(result)
}
},
//一个失败全部失败
reason => reject(reason)
)
})
})
}
Promise.race = function(promiseArr){
return new myPromise((resolve, reject) => {
promiseArr.forEach(promise => {
//这里利用了单决议的特性,参考上方构造函数,resolve只调用一次
myPromise.resolve(promise).then(resolve,reject)
})
})
}
接下来是自定义一些方法如resolveDelay,rejectDelay
Promise.resolveDelay = function(value, time){
return new Promise((resolve, reject)=>{
//注意setTimeout是在executor的内部
setTimeout(()=>{
if (value instanceof Promise) {
value.then(resolve, reject)
} else {
resolve(value)
}
}, time)
})
})
Promise.rejectDelay = function (reason, time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(reason)
}, time);
})
}