博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Promise这个折磨人的小妖精
阅读量:6804 次
发布时间:2019-06-26

本文共 8292 字,大约阅读时间需要 27 分钟。

前言

不得不说, promise 这玩意,是每个面试官都会问的问题,但是你真的了解promise吗?其实我也不了解,下面的内容都是我从掘金、知乎、《ECMAScript6入门》上看的博客文章等资料,然后总结的,毕竟自己写一遍,更有助于理解,如有错误,请指出 ~

什么是回调地狱 ?

在过去写异步代码都要靠回调函数,当异步操作依赖于其他异步操作的返回值时,会出现一种现象,被程序员称为 “回调地狱”,比如这样 :

// 假设我们要请求用户数据信息,它接收两个回调,假设我们要请求用户数据信息,它接收两个回调,successCallback 和 errCallback    function getUserInfo (successCallback, errCallback) {        $.ajax({            url : 'xxx',            method : 'get',            data : {                user_id : '123'            },            success : function(res) {                successCallback(res)    // 请求成功,执行successCallback()回调            },            error : function(err) {                errCallback(err)        // 请求失败,执行errCallback()回调            }        })    }复制代码

骗我 ? 这哪里复杂了,明明很简单啊,说好的回调地狱呢 ? 不急,继续看

假设我们拿到了用户信息,但是我们还要拿到该用户的聊天列表,然后再拿到跟某一“陌生”男人的聊天记录呢 ?

// getUserInfo -> getConnectList -> getOneManConnect()    getUserInfo((res)=>{        getConnectList(res.user_id, (list)=>{            getOneManConnect(list.one_man_id, (message)=>{                console.log('这是我和某位老男人的聊天记录')            }, (msg_err)=>{                console.log('获取详情失败,别污蔑我,我不跟老男人聊天')            })        }, (list_err)=>{            console.log('获取列表失败,我都不跟别人聊天')        })    }, (user_err)=>{        console.log('获取用户个人信息失败')    })复制代码

大兄弟,刺激不,三层嵌套,再多来几个嵌套,就是 “回调地狱” 了。这时候,promise来了。

Promise 简介

阮一峰老师的《ECMAScript 6入门》里对promise的含义是 : Promise 是异步编程的一种解决方案,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

简单来说,Promise就是对异步的执行结果的描述对象。

状态

  • pending (进行中)
  • fulfilled (已成功)
  • rejected (已失败)
1 : 只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。    2 : 一旦状态改变,就不会再变,任何时候都可以得到这个结果。    3 : Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected复制代码

知乎形象例子来说明promise

// 定外卖就是一个Promise,Promise的意思就是承诺// 我们定完外卖,饭不会立即到我们手中// 这时候我们和商家就要达成一个承诺// 在未来,不管饭是做好了还是烧糊了,都会给我们一个答复function 定外卖(){    // Promise 接受两个参数    // resolve: 异步事件成功时调用(菜烧好了)    // reject: 异步事件失败时调用(菜烧糊了)    return new Promise((resolve, reject) => {        let result = 做饭()	// 下面商家给出承诺,不管烧没烧好,都会告诉你	if (result == '菜烧好了') 	    // 商家给出了反馈	    resolve('我们的外卖正在给您派送了')	else 	    reject('不好意思,我们菜烧糊了,您再等一会')	})}// 商家厨房做饭,模拟概率事件function 做饭() {    return Math.random() > 0.5 ? '菜烧好了' : '菜烧糊了'}// 你在家上饿了么定外卖// 有一半的概率会把你的饭烧糊了// 好在有承诺,他还是会告诉你定外卖()    // 菜烧好执行,返回'我们的外卖正在给您派送了'    .then(res => console.log(res))    // 菜烧糊了执行,返回'不好意思,我们菜烧糊了,您再等一会'    .catch(res => console.log(res))复制代码

基本用法

Promise 对象是一个构造函数,用来生成一个Promise实例。

Promise构造函数接受一个函数作为参数,这个函数有两个参数,分别是resolve()和reject()。    resovle()函数是将Promise对象从pending变成fulfilled,在异步操作完成时执行,将异步结果,作为参数传递出去。    reject()函数是将Promise对象从pending变成rejected,在异步执行失败时执行,将报错信息,作为参数传递出去。    // 简单的一个promise实例, 来自阮一峰老师的es6 示例代码    const promise = new Promise((resolve, reject) => {        // some code         if(/* 异步执行成功 */) {            resolve(res)        } else {            reject(error)        }    })复制代码

then方法

Promise 有个.then()方法,then 方法中的回调在微任务队列中执行,支持传入两个参数,一个是成功的回调,一个是失败的回调,在 Promise 中调用了 resolve 方法,就会在 then 中执行成功的回调,调用了 reject 方法,就会在 then 中执行失败的回调,成功的回调和失败的回调只能执行一个,resolve 和 reject 方法调用时传入的参数会传递给 then 方法中对应的回调函数。

// 执行 resolve      let promise = new Promise((resolve, reject) => {        console.log(1)        resolve(3)    })    console.log(2)    promise.then((data)=>{        console.log(data)    }, (err)=>{        console.log(err)    })    // 1    // 2    // 3复制代码
// 执行 reject      let promise = new Promise((resolve, reject) => {        console.log(1)        reject()    })    promise.then(()=>{        console.log(2)    }, ()=>{        console.log(3)    })    // 1    // 3复制代码

then方法

[ 注意 : then方法中的回调是异步的!!!]

为什么上面第一个示例代码的结果是 1 -> 2 -> 3呢 ?传入Promise 中的执行函数是立即执行完的啊,为什么不是立即执行 then 中的回调呢?因为then 中的回调是异步执行,表示该回调是插入事件队列末尾,在当前的同步任务结束之后,下次事件循环开始时执行队列中的任务。

Promise 的回调函数不是正常的异步任务,而是微任务(microtask)。它们的区别在于,正常任务追加到下一轮事件循环,微任务追加到本轮事件循环。这意味着,微任务的执行时间一定早于正常任务

then方法的返回值是一个新的GPromise对象,这就是为什么promise能够进行链式操作的原因。then方法中的一个难点就是处理异步,通过setInterval来监听GPromise对象的状态改变,一旦改变,就是执行GPromise对应的then方法中相应的回调函数。这样回调函数就能够插入事件队列末尾,异步执行。复制代码
then有两个参数 : onFulfilled 和 onRejected        · 当状态state为fulfilled,则执行onFulfilled,传入this.value。当状态state为rejected,则执行onRejected,传入this.reason    · onFulfilled,onRejected如果他们是函数,则必须分别在fulfilled,rejected后被调用,value或reason依次作为他们的第一个参数    class Promise{        constructor(executor){...}        // then 方法 有两个参数onFulfilled onRejected        then(onFulfilled,onRejected) {            // 状态为fulfilled,执行onFulfilled,传入成功的值            if (this.state === 'fulfilled') {                onFulfilled(this.value);            };            // 状态为rejected,执行onRejected,传入失败的原因            if (this.state === 'rejected') {                onRejected(this.reason);            };        }    }复制代码

Promise的链式调用

由于promise每次调用then方法就会返回一个新的promise对象,如果该then方法中执行的回调函数有返回值,那么这个返回值就会作为下一个promise实例的then方法回调的参数,如果 then 方法的返回值是一个 Promise 实例,那就返回一个新的 Promise 实例,将 then 返回的 Promise 实例执行后的结果作为返回 Promise 实例回调的参数。

还记得刚开头说的那个“陌生”男人例子吗 ?这里我们用promise的链式操作重写下

// 原来的代码    getUserInfo((res)=>{        getConnectList(res.user_id, (list)=>{            getOneManConnect(list.one_man_id, (message)=>{                console.log('这是我和某位老男人的聊天记录')            }, (msg_err)=>{                console.log('获取详情失败,别污蔑我,我不跟老男人聊天')            })        }, (list_err)=>{            console.log('获取列表失败,我都不跟别人聊天')        })    }, (user_err)=>{        console.log('获取用户个人信息失败')    })        // Promise重写的代码    function handleAjax (params) {        return new Promise((resolve, reject)=>{            $.ajax({                url : params.url,                type : params.type || 'get',                data : params.data || '',                success : function(data) {                    resolve(data)                },                error : function(error) {                    reject(error)                }            })        })    }    const promise = handleAjax({        url : 'xxxx/user'    });    promise.then((data1)=>{        console.log('获取个人信息成功')       // 获取个人信息成功        return handleAjax({            url : 'xxxx/user/connectlist',            data : data1.user_id        });    })    .then((data2)=>{        console.log('获得聊天列表')        return handleAjax({            url : 'xxxx/user/connectlist/one_man',            data : data2.one_man_id        });    })    .then((data3)=>{        console.log('获得跟某男人的聊天')    })    .catch((err)=>{        console.log(err)    }) 复制代码

来自ES6的 Promise.prototype.then()

Promise 实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。

then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

采用链式的then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用

来自ES6的 Promise.prototype.catch()

Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。Promise对象状态变为resolved,则会调用then方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected,就会调用catch方法指定的回调函数,处理这个错误。另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。

Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获

一般来说,不要在then方法里面定义 reject 状态的回调函数(即then的第二个参数),总是使用catch方法。

来自ES6的 Promise.all()

Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.all([p1, p2, p3])复制代码

Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。

p的状态由p1、p2、p3决定,分成两种情况。(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。复制代码

来自ES6 的Promise.race()

Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.all([p1, p2, p3])复制代码

上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数

Promise.race方法的参数与Promise.all方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。

来自ES6 的Promise.resolve()

有时需要将现有对象转为 Promise 对象,Promise.resolve方法就起到这个作用

Promise.resolve('test')    // 等价于    new Promise(resolve => resolve('test'))    // 更多请看阮一峰老师的ES6 Promise对象复制代码

来自ES6 的Promise.reject()

Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。

const p = Promise.reject('出错了');    // 等同于    const p = new Promise((resolve, reject) => reject('出错了'))    p.then(null, function (err) {        console.log(err)    // 出错了    });        // 更多请看阮一峰老师的ES6 Promise对象复制代码

相关链接

阮一峰 ES6 :

知乎例子 :

掘金 卡姆爱卡姆 :

来自segmentfault 的GEEK作者 :

个人博客 :

转载地址:http://aznwl.baihongyu.com/

你可能感兴趣的文章
php正则
查看>>
用长按键重复输入 - Mac OS X Lion
查看>>
thinkphp 语言包丢失
查看>>
.net的XML对象序列化VS WCF中xml序列化问题
查看>>
基于JSP+SERVLET的新闻发布系统(一)
查看>>
jquery左边滚动,完毕后跳转回来
查看>>
ubuntu12 环境下编译freerdp
查看>>
[置顶] Bug 11775332 - cluvfy fails with PRVF-5636 with DNS response timeout error [ID 11775332.8]
查看>>
java中的上传下载----ajaxFileUpload+struts2
查看>>
Linux Mysql 1130错误解决
查看>>
为Google Reader守夜。。。
查看>>
android 组件内部实现触摸事件,更改背景
查看>>
OpenStreetMap/Google/百度/Bing瓦片地图服务(TMS)
查看>>
有关二叉树的相关实现:建树,遍历(递归与非递归实现)
查看>>
android ViewFlipper的使用
查看>>
Python 排序
查看>>
(莱昂氏unix源代码分析导读-49) 字符缓冲区
查看>>
android分享到新浪微博,认证+发送微博,
查看>>
Android--Menus
查看>>
Changing the Output Voltage of a Switching Regulator on the Fly
查看>>