Promise好像也不是很难

175 阅读9分钟

一、含义

The Promise object is used for asynchronous computations. Promis对象用作异步计算

A Promise represents a value which may be available now,or in the future, or never. 一个Promise.一个Promise表示一个现在、将来或用不可能可用的值。

按照用途来解释

主要用于异步计算

可以将异步操作队列化,按照期望的顺序执行,返回符合与其的结果

可以在对象之间传递和操作Promise,帮助我们处理队列

二、为什么有Promise

Javascript包含大量异步操作 有了Node.js之后,对异步的依赖进一步加剧了 异步操作的常见语法 事件帧听与响应:

document.getElementById('demo').addEventListener('click',start,false);

function start(){
}

//jQuery使用方法
$('#demo').on('click',start);
回调:

//比较常见的是AJAX
$.ajax('http://baidu.com',{
    success:function(res){
     //这里就是回调函数
}
})

//或者在页面加载完毕后回调
$(function(){
    //这里也是回调函数
})

三、异步回调的问题

嵌套层次很深,难以维护(回调地域)

无法正常使用return和throw

无法正常检索堆栈信息

多个回调之间难以建立联系

四、Promise简介

new Promise(
    // 执行器 executor
    function (resolve,reject){
        //一段很长的异步操作
        resolve();//数据处理完成
        reject();//数据处理出错
    }
).then(
    function A(){
        //成功,下一步
    },
    function B(){
        //失败,做相应处理
    }
    
)

Promise 是一个代理对象,它和原先要进行的操作并无关系。

它通过引入一个回调,避免更多的回调。

三个状态

Pending [待定] 初始状态

Fulfilled [实现] 操作成功

Rejected [被否决] 操作失败

特点

Promise状态发生改变,就会触发 .then()里的响应函数处理后续步骤。

Promise状态一经改变,不会再变。

流程图

new 一个Promise 传入执行器,异步操作。后续处理调用第一个then(),之后调用第二个then(),最终生成一个队列,每个then()都会返回一个新的Promise实例。当执行器executor执行器执行完毕,开始判断状态,调用then的函数,一个then执行完毕,又会执行下一个then,直至所有then()执行完。

五、最简单的实例

console.log('here we go');

new Promise(
    resolve => {
        setTimeout(()=>{
            resolve('hellooo ');
        },2000)
    }
).then(
    value => {
        console.log(value + 'world')
    }
)
两步执行的范例

console.log('here we go');

new Promise(
    resolve => {
        setTimeout(()=>{
            resolve('hello');
        },2000);
    }
).then(
    value => {
        console.log(value);
        return new Promise(
            resolve => {
                setTimeout(()=>{
                    resolve('world');
                },2000)
            }
        )
    }
).then(
    value =>{
        console.log(value +' ohhhhhhhhh')
    }
)

//执行结果
//here we go
//hello
//world ohhhhhhhhh

六、对已完成的Promise执行then

假如一个Promise已经完成了,再.then()会怎样?

console.log('start');

let promise = new Promise(
    resolve => {
        setTimeout(()=>{
            console.log('the promise fulfilled');
            resolve('hello');
        },1000);
    }
)

setTimeout(()=>{
    promise.then(
        value => {
            console.log(value + ' world');
        }
    )
},3000)

//结果
//start
//the promise fulfilled
//helloworld

在任何一个地方生成Promis队列之后,我们可以把它作为一个变量传递到其他地方。在后续可以追加任意多的then(),不管之前的promise是完成了还是没有完成,队列都会按照固定的顺序去执行。如果已完成,后面的then()也会得到之前promise的值。

七、then里不返回Promise

加入.then()的函数里面不返回新的Promise,会怎样?

console.log('here we go');

new Promise(
    resolve => {
        setTimeout(()=>{
            resolve('hello');
        },1000)
    }
).then(
    value => {
        console.log(value);
//增加立即执行函数
        (function(){
                return new Promise(
                    resolve => {
                        setTimeout(()=>{
                            console.log('nonononono')
                            resolve('nonono')
                        },2000)
                        
                    }
                )
        })()
        return false;
    }
).then(
    value => {
        console.log(value)
    }
)

//结果
//here we go
//hello
//false
//nonononono

八、引出.then()

.then()接受两个函数作为参数,分别代表fulfilled和rejected

.then()返回一个新的Promise实例,所以它可以链式调用

当前面的Promise状态改变时,.then()根据其最终状态,选择特定的状态响应函数执行

状态响应函数可以返回新的Promise,或其他值

如果返回新的Promise,那么下一级.then()会在新Promise状态改变之后执行

如果返回其他任何值,则会立即执行下一级.then()

.then()嵌套

因为 .then() 返回的还是Promise实例

会等里面的 .then()执行完,再执行外面的

对于我们来说,此时最好将其展开,会更好读

console.log('start');
new Promise(resolve => {
    console.log('step 1');
    setTimeout(()=>{
        resolve(100);
    },1000)
})
.then(
    value => {
        return new Promise(
            resolve=>{
                console.log('step1-1')
                setTimeout(()=>{
                    resolve(110);
                },1000)
            }
        ).then(
            value => {
                console.log('step 1-2');
                return value;
            }
        ).then(
            value => {
                console.log('step 1-3');
                return value;
            }
        )
    }
).then(
    value => {
        console.log(value);
        console.log('step 2');
    }
)

//结果
//start
//step 1
//1-1
//step 1-2
//step 1-3
//110
//step 2

//将嵌套的.then()提取出来
console.log('start');
new Promise(resolve => {
    console.log('step 1');
    setTimeout(()=>{
        resolve(100);
    },1000)
})
.then(
    value => {
        return new Promise(
            resolve=>{
                console.log('step1-1')
                setTimeout(()=>{
                    resolve(110);
                },1000)
            }
        )
    }
)
.then(
    value => {
        console.log('step 1-2');
        return value;
    }
).then(
    value => {
        console.log('step 1-3');
        return value;
    }
)
.then(
    value => {
        console.log(value);
        console.log('step 2');
    }
)

//结果相同
//start
//step 1
//1-1
//step 1-2
//step 1-3
//110
//step 2

九、四个问题

下面的四种Promise的区别是什么

问题一

问题二

问题三

doSomethingElse()是以执行的方式传入进去的,实际上传的是一个Promise实例。这种情况下,doSomething和doSomethingElse(undefined)几乎同时执行,它们是在同一个栈当中去执行的。因为doSomethingElse返回的是一个Promise实例,不是一个函数,在Promise规范定义当中,第一个.then()会被忽略掉。所以finalHandler最后执行时帧听的是doSomething的完成时间。doSomethingElse执行时间无关紧要,因为不处在队列当中。

问题四

问题来源

fexteam.gz01.bdysite.com/blog/2015/0…

十、错误处理

Promise会自动捕获内部异常,并交给rejected响应函数处理

第一种方式 throw new Error('错误信息').catch(message=>{}) (推荐)

console.log('start');

new Promise(
    resolve => {
        setTimeout(()=>{
            throw new Error('bye')
        },2000)
    }
).then(
    value => {
        console.log('hahah'+value)
    }
).catch(
    error => {
        console.log('Error'+error.message)
    }
)

//会报错,直接跳过.then进入.catch

第二种方式 reject('错误信息').then(null,message=>{})

console.log('start');

new Promise(
    (resolve,reject) => {
        setTimeout(()=>{
            reject('bye')
            //以下方法会跟第一种类似
            //throw new Error('bye')
        },2000)
    }
).then(
    value => {
        console.log('ok');
    },
    oh => {
        console.log('error'+oh)
    }
)

十一、错误和then连用

console.log('start');

new Promise(
    resolve => {
        setTimeout(()=>{
            resolve();
        },1000)
    }
).then(
    ()=>{
        console.log('start2');
        throw new Error('test Error');
    }
).catch(
    error => {
        console.log('I catch ',error)

        //下面这一行的注释将引发不同的走向
        //throw new Error('another error')
    }
).then(
    ()=>{
        console.log('arrive here');
    }
).then(
    ()=>{
        console.log('... and here');
        
    }
).catch(
    err => {
        console.log('error',err)
    }
)
结果

start
start2
I catch  Error: test Error
    at Promise.then (/home/leondon/Desktop/nodeJsDemo/express-middleware-demo/src/Promise.js:12:15)
arrive here
... and here

建议在所有队列最后都加上.catch(),以避免漏掉错误处理造成意向不到的问题

十二、Promise.all()

批量执行

Promise.all([p1,p2,p3,......])用于将多个Promise实例,包装成一个新的Promise实例。

返回的实例就是普通Promise

它接收一个数组作为参数

数组里可以是Promise对象,也可以是别的值,只有Promise会等待状态改变

当所有子Promise都完成,该Promise完成,返回值是全部值的数组

有任何一个失败,该Promise失败,返回值是第一个失败的子Promise的结果

console.log('here we go');

Promise.all([1,2,3]).then(
    all => {
        console.log('1:',all);
        return Promise.all([function(){
            console.log('ooxx');
        },'xxoo',false]);
    }
).then(
    all => {
        console.log('2:',all);
        let p1 = new Promise(
            resolve => {
                setTimeout(()=>{
                    resolve('I\'m P1')
                },1500)
            }
        )
        let p2 = new Promise(
            resolve => {
                setTimeout(()=>{
                    resolve('I\'m P2')
                },1450)
            }
        )
        return Promise.all([p1,p2]);
    }
).then(
    all => {
        console.log('3:',all);
        let p1 = new Promise(
            (resolve,reject) => {
                setTimeout(()=>{
                    resolve('I\'m P1');
                },1500)
            }
        )
        //时间最早,所以P2被触发
        let p2 = new Promise(
            (resolve,reject) => {
                setTimeout(()=>{
                    reject('I\'m P2')
                },1000)
            }
        )
        let p3 = new Promise(
            (resolve,reject) => {
                setTimeout(()=>{
                    reject('I\'m P3')
                },3000)
            }
        )
        return Promise.all([p1,p2,p3])
    }
).then(
    all => {
        console.log('all',all);
    }
).catch(
    error => {
        console.log('catch',error)
    }
)
结果

here we go
1: [ 1, 2, 3 ]
2: [ [Function], 'xxoo', false ]
3: [ 'I\'m P1', 'I\'m P2' ]
catch I'm P2

Promise.all()与.map连用

Promise.all()最常见就是和.map()连用

十三、实现队列

有时候我们不希望所有动作一起发生,而是按照一定顺序,逐个进行

使用forEach()函数

function queue(things){
    let promise = Promise.resolve();
    things.forEach(
        thing =>{
            promise = promise.then(()=>{
                return new Promise(resolve => {
                    doThing(thing,()=>{
                        resolve();
                    })
                })
            })
        }
    );
    return promise;
}

使用.reduce()

reduce()函数作用是遍历数组,从数组的一端到另一端


function queue(things){
    return things.reduce(
        (resolve,reject)=>{
            return Promise.then(()=>{
                return new Promise(resolve=>{
                    doThing(thing,()=>{
                        resolve();
                    })
                })
            })
        },Promise.resolve()
    );
}

十四、Promise.resolve()

返回一个fulfilled的Promise实例,或原始Promise实例

  • 参数w为空,返回一个状态为fulfilled的Promise实例
  • 参数是一个跟Promise无关的值,同上,不过fulfilled响应函数会得到这个参数
  • 参数为Promise实例,则返回该实例,不做任何修改
  • 参数是thenable,立刻执行它的.then()
console.log('start');

Promise.resolve().then(
    (value) => {
        console.log('Step1',value);
        return Promise.resolve('Hello');
    }
).then(
    value => {
        console.log(value,'world');
        return Promise.resolve(new Promise(resolve => {
            setTimeout(()=>{
                resolve('Good')
            },2000)
        }))
        
    }
).then(
    value => {
        console.log(value,'evening');
        return Promise.resolve({
            then(){
                console.log(', everyone')
            }
        })
    }
)

结果

start
Step1 undefined
helllllllll world
Good evening
, everyone

十五、Promise.reject()

返回一个rejected的Promise实例。


Promise.reject()不认thenable
let promise = Promise.reject('something wrong');

promise.then(
    ()=>{
        console.log('its ok');
    }
).catch(
    ()=>{
        console.log('no its not ok');
        
        return Promise.reject({
            then(){
                console.log('it will be ok');
                
            },
            catch(){
                console.log('not yet')
            }
        })
    }
)

结果

no its not ok
(node:22203) UnhandledPromiseRejectionWarning: #<Object>
(node:22203) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 3)
(node:22203) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

十六、Promise.reject()

类似Promise.all(),区别在于它有一个完成就算完成


let p1 = new Promise(resolve => {
    setTimeout(()=>{
        resolve('i am p1')
    },10000)
})

let p2 = new Promise (resolve => {
    setTimeout(()=>{
        resolve('i am p2')
    },2000)
})

Promise.race([p1,p2]).then(
    value =>{
        console.log(value);
    }
)

结果

[Running] node "/home/leondon/Desktop/nodeJsDemo/express-middleware-demo/src/Promise.js"
i am p2

[Done] exited with code=0 in 10.526 seconds

常见用法

  • 把异步操作和定时器放在一起
  • 如果定时器先触发,就认为超时,告知用户

十七、把回调包装成Promise

把回调包装成Promise最为常见。它有两个显而易见的好处

可读性更好

返回的结果可以加入任何Promise队列

十八、把任意异步操作包装成Promise

假设需求,用户点击按钮,弹出确认窗体

用户确认和取消有不同的处理

样式w恩提不能使用window.confirm()


//伪代码
let confirm = popupManager.confirm('您确定么?')
confirm.promise
.then(()=>{
    // do confirm staff
})
.catch(()=>{
    //do cancel staff
});

//窗体的构造函数
class Confirm{
    constructor(){
        this.promise = new Promise((resolve,reject)=>{
            this.confirmButton.onClick = resolve;
            this.cancelButton.onClick = reject;
        })
    }
}

十九、异步函数

async / await

  • 赋予Javascript以顺序手法编写异步脚本的能力
  • 既保留异步运算的无阻塞的特性,还继续使用同步写法
  • 还能正常使用 return/try/ catch
  • await只能等待一个Promise对象返回

function resolveAfter2Seconds(x){
    return new Promise(resolve => {
        setTimeout(()=>{
            resolve(x)
        },2000);
    })
}

async function f1(){
    var x = await resolveAfter2Seconds(10);
    console.log(x);//10
    
}
f1();