🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 什么是Promise Promise是异步编程的一种解决方案,比传统的解决方案-回调函数和事件等-更加合理且更加强大。它最早由社区提出并实现,ES6将其写进了语言标准,统一了用法,并原生提供了Promise对象。 所谓Promise,捡来来说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上来说,Promise是一个对象,从他可以获取一步操作的消息。Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。 # 几种常见异步编程方案 * 回调函数 * 事件监听 * 发布/订阅 * Promise对象 ## 拿回调函数说说事 1. 对于回调函数我们用Jquery的ajax获取数据时都是以回调函数方式获取的数据 ``` $.get(url, data => { console.log(data) ) ``` 2. 如果说当我们需要发送多个异步请求并且每个请求之间需要相互依赖 那这时我们只能以嵌套方式来解决形成 **"回调地狱"** ``` $.get(url, data1 => { console.log(data1) $.get(data1.url, data2 => { console.log(data2) ... }) }) ``` 这样一来,在处理越多的异步逻辑时,就需要越深的回调嵌套,这种编码模式的问题主要有以下几个: * 代码逻辑书写顺序与执行顺序不一致,不利于阅读与维护 * 异步操作的顺序变更时,需要大规模的代码重构 * 回调函数基本都是匿名函数,bug 追踪困难 * 回调函数是被第三方库代码(如上例中的 ajax )而非自己的业务代码所调用的,造成了 IoC 控制反转 ## Promise 处理多个相互关联的异步请求 1. Promise可以更直观的方式来解决 "回调地狱" ``` const request = url => new Promise((resolve, reject) => { $.get(url, data => { resolve(data) }) }) // 请求data1 request(url).then(data1 => { return request(data1.url); }).then(data2 => { return request(data2.url) }).then(data3 => { console.log(data3) }).catch(err => throw new Error(err)) ``` 2. 相信大家在 vue/react 都是用axios fetch 请求数据也都支持Promise API ``` import axios from 'axios'; axios.get(url).then(data => { console.log(data) }) ``` # 状态 Promise对象只有三种状态: * pending(未完成/等待中/进行中)- 表示还没有得到结果 * resolved(也称fulfilled)(已完成/已成功)- 在异步操作成功时调用,并将结果作为参数传递出去。 * rejected(失败/已失败)- 在异步操作失败时调用,并将报出的错误作为参数传递出去 注意promsie状态 只能由 pending => fulfilled/rejected, 一旦修改就不能再变 Promise对象有以下两个特点: 1. 对象的状态不受外界影响 Promise对象代表一个异步操作,只有异步操作的结果可以决定当前是哪种状态,其他任何操作都无法改变这个状态,这也是Promise名字的由来。所谓的“君子一言,驷马难追”,承诺将来某个状态,且该状态不会被其他因素改变。 2. 一旦状态改变就不会再变,任何时候都可以得到这个结果 Promise对象的状态改变只有两种可能(promise对象初始化状态为pending): * 当调用resolve(成功),pending => fulfilled * 当调用reject(失败),pending => rejected 只要这两种情况发生,状态就凝固了,不会再变,而是一直保持这种结果,这是就称为Resolved(已定型)。就算改变已经发生,在对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同。事件的特点是,如果错过了它,再去监听是得不到结果的。 # 基本用法 ``` let promise = new Promise((resolve, reject) => { // do something if (/* 异步执行成功 */) { resolve('请求成功') } else { reject('请求失败') } }) promise.then(value => { console.log('resolve', value) }, error => { console.log('reject', error) }) // 或者 promise.then(value => { console.log('resolve', value) }).catch(error => { console.log('reject', error) }) ``` >[info]Tip: 从基本用法的例子中我们看到Promise构造函数的参数是resolve和reject,并不是三种状态中的fulfilled和rejected,原因就是:resolved表示的是已结束(已定型),它包含fullfilled和rejected两种状态,但使用中,我们默认的将resolved当做fulfilled(成功)使用。 # 基本实现 首先能看出Promise是一个类或者说是一个构造器,构造器接收一个函数参数 ``` function Promise(executor) { } ``` Promise实例至少拥有then()和catch()方法 ``` Promise.prototype = { then: function() { }, catch: function() { } } ``` 构造函数内部回调构造函数传递过来的参数时,需要回传两个参数,分别是resolve和reject,而这两个参数各自都又是一个函数,所以构造函数内部需要有这样两个函数,以供回调参数时回传回去。所以修改Promise如下: ``` function Promise(executor) { // 定义resolve函数 function resolve() { } // 定义reject函数 function reject() { } // 回调,并且回传resolve和reject executor(resolve, reject) } ``` 观察下面代码片段: ``` if (/* 异步执行成功 */) { resolve('请求成功') } else { reject('请求失败') } ``` 可以得出,在调用resolve()和reject()时需要传入参数,所以这两个方法还需要参数 ``` function Promise(executor) { /** * 成功回调函数 * @param value 回调参数 */ function resolve(value) { } /** * 失败回调函数 * @param reason 回调参数 */ function reject(reason) { } // 回调,并且回传resolve和reject executor(resolve, reject) } ``` 观察一下代码片段: ``` promise.then(value => { console.log('resolve', value) // resolve 请求成功 }, error => { console.log('reject', error) // reject 请求失败 }) ``` 得出then()方法接收两个函数,作为参数。 第一个函数参数为resolved后被调用的参数,并且需要回传结果值 第二个函数参数为rejected后被调用的参数,并且需要回传结果值,这个参数可以省略 于是我们修改上面的then()方法 ``` Promise.prototype = { then: function(onResolved, onRejected) { } } ``` 观察一下代码片段: ``` if (/* 异步执行成功 */) { resolve('请求成功') } else { reject('请求失败') } ``` ``` promise.then(value => { console.log('resolve', value) // resolve 请求成功 }, error => { console.log('reject', error) // reject 请求失败 }) ``` 可以看出,构造`Promise`时,函数内部根据异步执行结果确定调用`resolve`还是`reject`,这个毋庸置疑。现在的问题在于,调用了`resolve`之后`then()`方法的第一个参数如何被调用?失败之后`then()`方法的第二个参数又如何被调用? 答案是:我们可以把`then(onResolved, onRejected)`方法的两个函数参数`onResolved`和`onRejected`当做`Promise`的实例属性暂存起来,然后再构造函数内部的`resolve()`方法和`reject()`方法被调用时,调用这两个暂存起来的函数即可实现 ``` function Promise(executor) { var self = this self.onResolved = null // 保存onResolved self.onRejected = null // 保存onRejected /** * 成功回调函数 * @param value 回调参数 */ function resolve(value) { self.onResolved(value) // 调用then()方法的第一个函数参数 } /** * 失败回调函数 * @param reason 回调参数 */ function reject(reason) { self.onRejected(reason) // 调用then()方法的第二个函数参数 } // 回调,并且回传resolve和reject executor(resolve, reject) } Promise.prototype = { then: function(onResolved, onRejected) { this.onResolved = onResolved this.onRejected = onRejected } } ``` 到此我们实现了一个简单到不能再简单的Promise,赶紧撸段代码测试一把。 使用setTimeout模拟异步,通过传入的isSuccess参数值模拟异步成功与否 ``` var promise = isSuccess => new Promise((resolve, reject) => { setTimeout(() => { if (isSuccess) { resolve('请求成功了') } else { reject('请求失败了') } }, 1000) }) ``` 传入true,等待1s打印成功;传入false,等待1s打印失败 ``` promise(true).then(value => { console.log('成功', value) }, error => { console.log('失败', error) }) promise(false).then(value => { console.log('成功', value) }, error => { console.log('失败', error) }) ``` 下面我们整体看一下实现的代码,我去掉了所有的注释,只保留代码部分 ``` function Promise(executor) { var self = this self.onResolved = null self.onRejected = null function resolve(value) { self.onResolved(value) } function reject(reason) { self.onRejected(reason) } executor(resolve, reject) } Promise.prototype = { then: function(onResolved, onRejected) { this.onResolved = onResolved this.onRejected = onRejected } } ```