1.什么是Promise
一项技术不会凭空产生,都是为了解决某些实际的问题而出现。了解技术产生的背景,可以让我们更好的知道他擅长解决什么问题,哪些场景我们可以利用他来解决。那么就让我们一步一步来揭开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 就完成啦