从手写一个符合Promise/A+规范Promise来深入学习Promise
栏目: JavaScript · 发布时间: 6年前
内容简介:一项技术不会凭空产生,都是为了解决某些实际的问题而出现。了解技术产生的背景,可以让我们更好的知道他擅长解决什么问题,哪些场景我们可以利用他来解决。那么就让我们一步一步来揭开promise神秘的面纱。首先我们来了解一下promise。promise英语的意思是:诺言。Promise是抽象异步处理对象及其对其进行各种操作的组件。用大白话说,promise就是用来解决异步回调问题的。在没有promise 之前,前端处理ajax请求通常是这样的:
一项技术不会凭空产生,都是为了解决某些实际的问题而出现。了解技术产生的背景,可以让我们更好的知道他擅长解决什么问题,哪些场景我们可以利用他来解决。那么就让我们一步一步来揭开promise神秘的面纱。
1.1.什么是promise
首先我们来了解一下promise。promise英语的意思是:诺言。Promise是抽象异步处理对象及其对其进行各种操作的组件。用大白话说,promise就是用来解决异步回调问题的。
1.2.promise解决了什么问题
在没有promise 之前,前端处理ajax请求通常是这样的:
function fetchData (callback) { $.ajax({ type: 'GET', url:'xxx', data: data, success: function (res) { callback(res) // 回调函数 } }) } 复制代码
这只是处理一个ajax请求,可很多时候,我们回面临第一个请求回来的结果,是第二次请求的参数,或者我们想一个极端的例子,前一次请求的结果是第二次请求的参数,那么我们的代码可能是这样的:
function fetchData (callback) { $.ajax({ type: 'GET', url: 'xxx', data:data, success: function (res) { if (res.data) { const params1 = res.data; $.ajax({ type:'GET', url: 'xxx', data: params1, success: function (res) { if (res.data) { const params2 = res.data $.ajax({ type:'GET', url: 'xxx', data: paramsw, success: function (res) { if (res.data) { const params3 = res.data; ...... } } }) } } }) } } }) } 复制代码
上面这个就是回调地狱。代码的可读性,可维护性大打折扣。promise就是为他而生的,通过promise我们可以像写同步那样写异步方法。同样是上面的场景我们完全可以用另一种方法:
function fetchData (url, data) { return new Promise((resolve, reject) => { $.ajax({ type: 'GET', url:url, data: data, success: function (res) { resolve(res) }, fail: function (err) { reject(res) } }) }) } fetchData(url,data).then(res => { const url1 = 'yyyy'; const data1 = res.data; return fetchData(url1,data1) }).then(res => { const url2 = 'zzz'; const data2 = res.data return fetchData(url2, data2) }).then(res => { const url3 = 'wwww'; const data3 = res.data; render() }).catch(err => { console.log(err) }) 复制代码
这样似乎美观了那么一丢丢,但是不要着急,我们一步一步来。
2.promise的实际应用
上一章我们主要探讨了promise的背景知识,简单的知道了promise使用的场景,这章我们就来跟进一步的学习promise
2.1 promised的状态
promise有三种状态:
- pending
- fulfilled
- rejected 如下图: promise对象的状态,从Pending转换为Fulfilled或Rejected之后, 这个promise对象的状态就不会再发生任何变化。
2.2 promise是异步的么?
如下代码:你觉得输出的结果是什么呢?
var promise = new Promise(function (resolve){ console.log("1"); //1 resolve(2); }); promise.then(function(value){ console.log(value); // 2 }); console.log("3"); // 3 复制代码
实际上执行上面的代码会输出下面的内容
1 3 2 复制代码
你会很好奇为什么会是这样?
由于JavaScript代码会按照文件的从上到下的顺序执行,所以最开始 <1> 会执行,然后是 resolve(2); 被执行。这时候 promise 对象的已经变为确定状态,FulFilled被设置为了 2 。
由于 promise.then 执行的时候promise对象已经是确定状态,从程序上说对回调函数进行同步调用也是行得通的。
但是即使在调用 promise.then 注册回调函数的时候promise对象已经是确定的状态,Promise也会以异步的方式调用该回调函数,这是在Promise设计上的规定方针。
因此 <3> 会最先被调用,最后才会调用回调函数 <2> 。
2.2 promise的链式调用
在前面的<1.2>中我们简单介绍了下promise的链式调用,在这里我们再次深入理解一下 看下面的代码
function task () { return new Promise((resolve, reject) => { setTimeout(() => { resolve('task') },1000) }) } task().then((res) => { console.log(res) return 'taskB' }).then(res => { console.log(res) return 'taskC' }).then(res => { console.log(res) throw new Error() }).catch(e => { console.log(e) return 'taskD' }).then(res => { console.log(res) }) 复制代码
哈哈,那么问题来啦?上面会输出什么呢?
哈哈,是不是很困惑catch后怎么又进入then()了呢? 实际上前面我们已经说过,promise有三种状态pending,fulfilled,rejected,一旦从pending态到fulFilled,或者从pending态到rejected,其状态都是不可逆的。因此,.then()中每次return出去的都是一个新的promise,而不是this. 有图有真相(虽然图是我盗的,说明问题就好):
那么对于异常情况下promise的流程是这个样子的:
3.尝试写出符合Promse/A+规范的promise
前两小节介绍了promise 的背景,及promise的更深入的学习,这节我们通过自己动手实现一个符合promise/A+规范的promise来彻底弄懂promise
3.1 promise 构造函数
通过2.2的探究我们知道当你创建一个new Promise()的时候,实际上在new Promise()内部是同步执行的,也就是说
const promise = new Promise((resolve,reject) => { console.log(1) }) 复制代码
当代码执行到console.log(1) 的时候,1会被立马打印出来。也就是说,promise的构造函数中有一个executor的函数,他会立马执行。因此:
function Promise (executor) { executor() } 复制代码
我们有向executor中传入了两个函数,resolve和reject,因此我们来进一步完善我们的构造函数:
function Promise(executro) { function resolve () {} function reject () {} executor(resolve, reject) } 复制代码
我们知道promise中有三种状态,那么我们需要一个属性来控制这个状态,我们添加一个status的属性吧:
function Promise(executor) { let self = this; self.status = 'pending' // 初始状态为pending function resolve() {} function reject() {} } 复制代码
当执行resolve函数的时候,status的状态应该从penging态转为resolved,同时存储resolve函数的值,同样的当执行reject函数的时候,status的状态应该从pengding态转为rejected,同时我们存储reject的值,代码入下:
function Promise (executor) { let self = this; self.status = 'pending' self.value = undefined self.reason = undefined self.onResolved = [] // 3.2小节添加 self.onRejected = [] // 3.2小姐添加 function resolve (value) { if (self.status === 'pending') { self.status = 'resolved' self.value = value self.onResolved.forEach(fn => fn()) } } function reject (reason) { if (self.status === 'pending') { self.status = 'rejected' self.reason = reason self.onRejected.forEach(fn => fn()) } } // 处理下异常 try { executor(resolve, reject) } catch (e) { reject(e) } } 复制代码
nice ^_^ 现在我们的Promise的构造函数就基本能满足我们的需求啦。
3.2 promise then方法
Promise/A+规范中,每个promise都有一个then方法,该方法返回的还是一个promise.这个then方法实在实例上调用,因此该then方法应该在promise的原型上,同样的我们需要根据promsie的状态来进行相应的处理,不同的是,当status是pending态的时候我们要收集resolve和reject,同时在构造函数中增加相应的处理,代码如下:
Promise.prototype.then = function (onfulfilled, onrejected) { // 这里坐下简单的处理 onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : val => val onrejected = typeof onrejected === 'function' ? onrejected : err => {throw err} let promise2 = new Promise((resolve, reject) => { if (self.status === 'resolved') { // onfulfilled(self.value) 3.2 let x = onfulfilled(self.value) // 3.3 resolvePromise(promise2, x, resolve, reject) // 3.3 } if (self.status === 'rejected') { // onrejected(self.reason) 3.2 let x = onrejected(self.reason) // 3.3 resolvePromise(promise2, x, resolve, reject) // 3.3 } if (self.status === 'pending') { self.onResolved.push(function () { setTimeout(() => { // onfulfilled(self.value) 3.2 let x = onfulfilled(self.value) // 3.3 resolvePromise(promise2, x, resolve, reject) // 3.3 },0) }) sef.onRejected.push(function () { setTimeout(() => { // onrejected(self.reason) 3.2 let x = onrejected(self.reason) // 3.3 resolvePromise(promise2, x, resolve, reject) // 3.3 }, 0) }) } }) return promise2 } 复制代码
我们的then 函数就实现啦,最重要的就是对pending态的处理
3.3 onfullfiled和onrejected返回结果的处理
我们在3.2小节留了一个坑,你会发现,新的Promise构造函数中传入的resolve,reject好像没有用到啊。那么这小节就是要填这个坑的。那我们会发现then方法中的onResolved和onRejected实际上分别是在Promise构造函数中的resolve 和reject中处理的。实际上我们会面临以下集中情况:
let promise = new Promise((resolve, reject) => { setTimeout(() => { resolve(1) },0) }) promise.then((res) => { return res },(err) => { // 直接 reject reject(err) }).then(res => { // 1. 直接返回一个变量 return res // 2. 返回一个对象或者函数 return obj // 3. 循环引用 }) 复制代码
因此我们对3.2的代码改造,我直接写在3.2的代码中加上3.3的标记,我们还要给出对不同返回值的解析处理
function resolvePromise(promise2, x, resolve, reject) { // 判断是否循环引用 if (x === promise2) { return reject(new TypeError('循环引用')) } // 判断是否为对象或者函数 if (x != null && (typeof x === 'object' || typeof x === 'function')) { try { let then = x.then // 判断是否为promise if (typeof then === 'function') { then.call(x, (y) => { resolvePromise(promise2, y, resolve, reject) }, (e) => { reject(e) }) } else { resolve(x) } } catch (e) { reject(e) } } else { resolve(x) } } 复制代码
这下我们的promise 就完成啦
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 只会用就out了,手写一个符合规范的Promise
- 手写一款符合Promise/A+规范的Promise
- 如何判断URL格式是否符合规范?
- [译] 打造符合用户期望的应用质量
- 符合语言习惯的 Python 优雅编程技巧
- 实现一个符合Promise/A+规范的Promise
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。