Promise到底解决了什么问题啊

在js环境下,我们编程大多数是异步编程,在异步编程中使用最多的是回调函数,然后就会形成回调地狱。

我们会发现编程中的几个大问题:

1.回调配合着嵌套会产生回调地狱问题,
2.由于回调存在着依赖反转,在使用带三方提供的方法时,存在信任问题
3.当我们不写错误的回调函数时,会存在异常无法捕获
4.导致我们性能更差,本来可以一起做的但是回调后,导致多件事情顺序执行,用的时间更多。


So,怎么解决?
首先解决信任问题:将程序包装进一个回调函数,将这个回调交给一个团体,希望他会做正确的事情并且调用这个回调。但是如果我们希望的是它可以返回给我们一个可以知道他什么时候完成的能力,然后我们的代码可以决定下一步能做什么。

上述规范被称为:Promise


什么是Promise?

promises是异步编程的一种解决方法:从语法上讲,promise是一个对象,他可以获取异步操作的消息;本意讲,他是承诺,承诺他过一段时间会给你一个结果。

promise有三种状态:pending(等待),fulfiled(成功),rejected(失败),状态一旦改变就不会再变。创造promise实例后会立即执行


Promise基本使用

promise是一个构造函数,自己身上有all,reject,resolve…方法,原型上游catch,then…

let p = new Promise((resolve,reject) => {
    // ...异步操作
    setTimeout(() => {
        console.log('执行完成')
        resolve('成功啦');
    },2000);
});

表面上康康,Promise简化了层层回调的写法,实际上promise的精髓是状态,用维护状态传递状态的方式,来使得回调函数能够及时调用,它比传递callback函数简单灵活。

So,like this:

function myAjax(url,type = 'GET'){
    return new Promise((resolve,reject)=>{
        $.ajax({
            type,
            url,
            success:resolve,
            erro:reject
        })
    })
}
myAjax('xxx')
.then(req=>{
    renderList(req.data.list)
    return
    myAjax('xxx')
},erro => {console.log('erro')}
 ).then(req=>{
     return myAjax('xxx')
 },erro => {console.log('erro')}
)

通过Promise很好的解决异步问题,使得异步过程同步化。

reject:then中捕捉执行失败情况

let p = new Promise((resolve,reject)=>{
    setTimeout(function(){
        var num = Math.ceil(Math.random()*10);
        if(num > 5){
            resolve(num);
        }
        else{
            reject('数字太大了')
        }
    },2000);
});
p.then((data)=>{
    console.log('resolved',data)
},(err)=>{
    console.log('rejected',err)
});

catch:用来指定reject的回调,代码抛出异常不会报错卡死JS,进到catch中

p.then((data) => {
 console.log('resolved',data);
}).catch((err) => {
 console.log('rejected',err);
});

all:谁最慢谁回调,接收一个数组参数,里面的值最终返回Promise对象,并行的异步操作能力。在所有异步操作执行完毕后才执行回调

let Promise1 = new Promise(function(resolve, reject){})
let Promise2 = new Promise(function(resolve, reject){})
let Promise3 = new Promise(function(resolve, reject){})
let p = Promise.all([Promise1, Promise2, Promise3])
p.then(funciton(){
 // 三个都成功则成功 
}, function(){
 // 只要有失败,则失败
})

上述操作是并行的执行多个异步操作,并且在一个回调中处理所有的返回数据,有个场景:游戏类的素材很多,打开网页,预先加载要用的各种资源图片啊flash,各种静态资源,所有的都加载完成后在进行页面初始化,在这可以解决时间的性能问题,我们不需要再把每个异步过程同步过来


Promise解决回调信任问题

把一个回调传入工具foo()时,可能会出现的问题:
调用回调过早,
调用回调过晚(没调)
调用回调次数过多过少
未能传递所需的参数,环境
吞掉可能出现的错误和异常

Promise的特性为这些问题提供一个可复用的答案


调用过早

根据定义,Promise 就不必担心这种问题,因为即使是立即完成的 Promise
(类似于 new Promise(function(resolve){ resolve(42); }) )也无法被同步观
察到。
也就是说,对一个 Promise 调用 then()的时候,即使这个 Promise 已经决
议,提供给 then()的回调也总会被异步调用。
调用过晚
Promise 创建对象调用 resolve()或 reject()时,这个 Promise 的 then()注册的
观察回调就会被自动调度。可确信,这些被调度的回调在下一个异步事件
点上一定会被触发。
同步查看是不可能的,所以一个同步任务链无法以这种方式运行来实现按
照预期有效延迟另一个回调的发生。也就是说,一个 Promise 决议后,这
个 Promise 上所有的通过 then()注册的回调都会在下一个异步时机点上依
次被立即调用。这些回调中的任意一个都无法影响或延误对其他回调的调
用。

p.then( function(){
 p.then( function(){
 console.log( "C" );
 });
 console.log( "A" );
} );
p.then( function(){
 console.log( "B" );
});
// A B C

这里,“C” 无法打断或抢占“B”,这是因为 Promise 的运作方式。

Promise 调度技巧
有很多调度的细微差别。这种情况下,两个独立 Promise 上链接的回调的
相对顺序无法可靠预测。
如果两个 Promise p1 和 p2 都已经决议,那么 p1.then(), p2.then()应该
最终会制调用 p1 的回调,然后是 p2。但还有一些微妙的场景可能不是这
样。

var p3 = new Promise( function(resolve, reject){
 resolve( "B" );
});
var p1 = new Promise(function(resolve, reject){
 resolve( p3 );
})
p2 = new Promise(function(resolve, reject){
 resolve( "A" );
})
p1.then( function(v){
 console.log( v );
})
p2.then( function(v){
 console.log( v );
})

// A B , 而不是像你认为的 B A

p1 不是用立即值而是用另一个 promise p3 决议,后者本身决议为值“B”。
规定的行为是把 p3 展开到 p1,但是是异步地展开。所以,在异步任务队列
中,p1 的回调排在 p2 的回调之后。
所以避免多个Promise


回调未调用
首先,没有任何东西(甚至 JS 错误)能阻止 Prmise 向你通知它的决议(如
果它决议了的话)。如果你对一个 Promise 注册了一个完成回调和一个拒
绝回调,那么 Promise 在决议时总是会调用其中一个。
当然,如果你的回调函数本身包含 JS 错误,那可能就会看不到你期望的结
果。但实际上回调还是被调用了。后面讨论,这些错误并不会被吞掉。
但是,如果 Promise 永远不决议呢?即使这样,Promise 也提供了解决方
案。其使用了一种称为竟态的高级抽象机制:

// 用于超时一个 Promise 的工具
function timeoutPromise(delay){
 return new Promise( function(resolve, reject){
 setTimeout(function(){
 reject("Timeout!");
 }, delay)
 })
}
// 设置 foo()超时
Promise.race( [
 foo(),
 timeoutPromise( 3000 );
])
.then(
 function(){
 // foo() 及时完成!
 },
 function(err){
 // 或者 foo()被拒绝,或者只是没能按时完成
 // 查看 err 来了解是哪种情况
 } )

我们可保证一个 foo()有一个信号,防止其永久挂住程序。
调用次数过少或过多
根据定义,回调被调用的正确次数应该是 1。“过少”的情况就是调用 0
次,和前面解释过的“未被”调用是同一种情况。
“过多”容易解释。Promise 的定义方式使得它只能被决议一次。如果出
于某种原因,Promise 创建代码试图调用 resolve()或 reject()多次,或者试
图两者都调用,那么这个 Promise 将只会接受第一次决议,并默默地忽略
任何后续调用。
由于 Promise 只能被决议一次,所以任何通过 then()注册的(每个)回调
就只会被调用一次。
当然,如果你把同一个回调注册了不止一次(比如 p.then(f); p.then(f)),
那头被调用的次数就会和注册次数相同。响应函数只会被调用一次。
未能传递参数/环境值Promise 至多只能有一个决议值(完成或拒绝)。
如果你没有用任何值显式决议,那么这个值就是 undefined,这是 JS 常见
的处理方式。但不管这个值是什么,无论当前或未来,它都会被传给所有
注册的(且适当的完成或拒绝)回调。
还有一点需要清楚:如果使用多个参数调用 resovel()或者 reject()第一个参
数之后的所有参数都会被默默忽略。
如果要传递多个值,你就必须要把它们封装在一个数组或对象中。
对环境来说,JS 中的函数总是保持其定义所在的作用域的闭包,所以它们
当然可继续你提供的环境状态。


吞掉错误或异常

如果在 Promise 的创建过程中或在查看其决议结果过程中的任何时间点上
出现了一个 JS 异常错误,比如一个 TypeError 或 RefernceError,那这个异
常就会被捕捉,并且会使这个 Promise 被拒绝。


但是 Promise 并没有完全摆脱回调。它们只是改变了传递回调的位置。我们并不是把回调传递给 foo(),而是从 foo()得到某个东西,然后把回调传给这个东西。
但是,为什么这就比单纯使用回调更值得信任呢?

关于 Promise 的很重要但是常常被忽略的一个细节是,Promise 对这个问
题已经有一个解决方案。包含在原生 ES6 Promise 实现中的解决方案就是

Promise.resolve()

如果向 Promise.resolve()传递一个非 Promise、非 thenable 的立即值,就会
得到一个用这个值填充的 promise。下面这种情况下,promise p1 和
promise p2 的行为是完全一样的:

var p1 = new Promise( function(resolve, reject){
 resolve( 42 ); 
} )
var p2 = Promise.resolve(42);

而如果向 Promise.resolve() 传递一个真正的 Promise,就只会返回同一个promise

var p1 = Promise.resolve( 42 );
var p2 = Promise.resolve( p1 );
p1 === p2; // true

如果向 Promise.resolve()传递了一个非 Promise 的 thenable 值,前者会试
图展开这个值,而且展开过程会持续到提取出一个具体的非类 Promise 的
最终值。

var p = {
 then: function(cb){
 cb( 42 );
 }
};
// 这可以工作,但只是因为幸运而已
p.then(
 function fulfilled(val){
 console.log( val ); //42
 },
 function rejected(err){
 // 永远不会到这里
 } )

但是,下面又会怎样呢?

var p = {
 then: function(cb, errcb){
 cb(42);
 errcb("evil laugh");
 }
};
p.then(
 function fulfilled(val){
 console.log( val ); //42
 },
 function rejected(err){
 // 啊,不应该运行!
 console.log( err ); // 
 } )

尽管如此,我们还是都可把这些版本的 p 传给 Promise.resolve(),然后就
会得到期望中的规范化后的安全结果:

Promise.resolve(p)
.then(
 function fulfilled(val){
 console.log(val); //42
 },
 function rejected(err){
 // 永远不会到这里
 } )

Promise.resolve()可接受任何 thenable,将其解封完它的非 thenable 值。从
Promise.resolve()得到的是一个真正的 Promise,是一个可信任的值。如果你
传入的已经是真正的 Promise,那们你得到的就是它本身,所以通过Promise.resolve()过滤来获得可信任性完全没有坏处。
假设我们要调用一个工具 foo(),且不确定得到的返回值是否是一个可信任
的行为良好的 Promise,但我们可知道它至少是 一 个 thenable 。
Promise.resolve()提供了可信任的 Promise 封装工具,可链接使用:

// 不要这么做
foo(42)
.then(function(v) {
 console.log( v );
});
// 而要这么做
Promise.resolve( foo(42) )
.then( function(v){
 console.log(v)
})

对于用Promise.resolve() 为所有函数的返回值都封装一层。另一个好处是,
这样做很容易把函数调用规范为定义良好的异步任务。如果 foo(42)有时会
返回一个立即值,有时会返回 Promise,那么 Promise.resolve(foo(42))就能
保证总返回一个 Promise 结果。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值