最近一直私下在看Android项目,前端这一块没怎么仔细研究。昨天在写重构公司前端项目的时候,我发现一旦有异步的任务,脑海里面条件反射通常的出现promise的字样。重构的多了 心就就在纳闷:既然promise这么好用,我能不能本身手写一个promise呢?我思索了半天,按照本身的想法模拟了出来,可是和一位大佬交流的时候,他说个人写法并无遵循Promise/A+规范。当时我内心是懵逼的,加班的时候就一直想写这个问题。javascript
咱们都知道,在javascript里面全部的代码都是单线程执行的。因为这种“单线程”的弊端,全部javascript重的网络请求、浏览器事件等都只能用异步来执行。传统的异步实现的方式都是用回调函数来实现的,例如:前端
request.onreadystatechange = function () {
if (request.readyState === 4) {
if (request.status === RESULT_OK) {
return success(req.responseText);
}
return fail(req.status);
}
}
复制代码
这样虽然比较直观,可是并不符合程序员的审美习惯,代码的复用性也很低。按照如今主流的代码风格来讲,一份优秀的代码模版就应该是链表形式的。java
// Android写习惯了,通常封装网络都是okhttp 。不用理我 = =
okhttp.request()
.onSuccesul()
.onFail();
复制代码
因而promise应运而生。程序员
promise的中文释意为:承诺。我以为这个翻译很能凸显这个对象的含义。数组
promise里面有三个状态:promise
为何说,承诺这个解释很能凸显promise对象的特性呢?咱们来为您谈谈promise里面的两个特色:浏览器
一、 对象不能被任何手段来更改。怎么解释呢?只要是你下了承诺,就不能受任何外界环境的干扰,只受到一步操做的结果来决定。 二、 一旦状态改变,就不会再变,而且任什么时候候均可以获得这个结果。缓存
从上面的特性里面,是否是更加加深了对承诺的理解呢!bash
前面说了这么多,都是傻把式,下面咱们就用代码体验一把promise的快感吧 。微信
new Promise((resolve,reject)=>{
let randomNumber = Math.random()
console.log(randomNumber)
if(randomNumber>0.5){
resolve('success!!!')
}else{
reject('fail!!!')
}
}).then(res=>{
console.log(res);
},error=>{
console.log(error);
})
复制代码
从上面的代码咱们能够比较清楚的发现,Promise接收两个参数:resolve和reject。resolve函数的做用是,将Promise对象的状态从“进行中”变为“成功”( pending => resolved),在异步操做成功时调用,并将异步操做的结果,做为参数传递出去;reject函数的做用是,将Promise对象的状态从“进行中”变为“失败”( pending=>rejected),在异步操做失败时调用,并将异步操做报出的错误,做为参数传递出去。
而且在Promise对象实例生成后,能够用then来分别指定完成后的resolve和reject。而且then里面的reject是可选项。
对于promise,我以为主要有三个点须要给读者讲一下,来看下面一段代码:
let promise = new Promise((resolve,reject)=>{
let randomNumber = Math.random()
console.log(randomNumber)
if(randomNumber>0.5){
resolve('success!!!')
}else{
reject('fail!!!')
}
})
复制代码
咱们发现单独运行这一段代码的话,console依然被打印出来了。这说明promise是建立的时候就被运行的。 再来看一段代码:
let promise = new Promise((resolve,reject)=>{
let randomNumber = Math.random()
if(randomNumber>0.5){
resolve('success!!!')
}else{
reject('fail!!!')
}
console.log(randomNumber)
}).then(res=>{
console.log(res);
},error=>{
console.log(error)
})
复制代码
效果以下:
return resolve('success!!!');
return reject('fail!!!');
复制代码
Promise 实例具备then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的做用是为 Promise 实例添加状态改变时的回调函数。then里面有两个回调函数,前者返回resolve的回调函数,后者是可选值。而且then返回返回一个新的promise,这样就能一直使用链式结构了。
catch能够当作是then(null/undefined, reject)的别名 专门用来指定错误发生时的回调函数。那么为何要设计这个属性呢?主要有两个方面,首先来看下面一段代码:
// 普通then
.then(res=>{
console.log(res);
},error=>{
console.log(error)
})
//catch
.then(res=>{
console.log(res);
})
.catch(error=>{
console.log(error)
})
复制代码
明显第二个相对前一个会更加优雅一点。
咱们再来看一段代码:
// 普通then
.then(res=>{
throw Error("have exception")
console.log(res);
},error=>{
console.log(error)
})
// catch
.then(res=>{
throw Error("have exception")
}).catch(error=>{
console.log(error)
})
复制代码
运行上面的代码咱们很清楚的发现catch能捕获到then的异常,可是then的reject回调里面并不能捕获到resolve的异常。这在必定程度上保证了代码的正常运行顺序。
finally是es2018才引入的属性,无论 Promise 对象最后状态如何,都会执行的操做。其本质也是then方法的特性。
promise
.finally(() => {
// ...
});
// 等同于
promise
.then(
result => {
// ...
return result;
},
error => {
// ...
throw error;
}
);
复制代码
Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。其基础的语法是:
const p = Promise.all([p1, p2, p3]);
复制代码
Promise.all方法接受一个数组做为参数,p一、p二、p3都是 Promise 实例,若是不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。 关于p的状态,主要由接收的promise数组决定的 (1)只有p一、p二、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p一、p二、p3的返回值组成一个数组,传递给p的回调函数。 (2)只要p一、p二、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
这里值得咱们注意的一点是,若是p1,p2,p3中有错误 而且有本身的catch方法的时候。会调用其本身的catch,当没有catch方法的时候才会抛给p的catch来处理
Promise.race方法一样是将多个 Promise 实例,包装成一个新的 Promise 实例。其基础的语法是:
const p = Promise.race([p1, p2, p3]);
复制代码
和all方法不一样的是: (1)只有p一、p二、p3的状态都变成rejected,p才会变成rejected (2) 若是p一、p二、p3中有一个状态变成fulfilled,p的状态就会变成fulfilled 并将首次变成fulfilled的返回值返回。
前面大概都将所谓的promise的用法给描述了一遍,大概写一两个例子就能感觉出promise的奥秘。那么若是让你本身来写一个promise,你应该如何来写呢?
这是我下班后写的一个Promise:
const PENDING = "pending";
const RESOLVE = "resolve";
const REJECTED = "rejected";
function JPromise(fn){
const that = this;
that.state = PENDING;
that.value = null;
that.resolvedCallbacks = [];
that.rejectedCallbacks = [];
function resolve(value){
if(that.state === PENDING){
that.state = RESOLVE;
that.value = value;
that.resolvedCallbacks.map(cb=>cb(that.value));
}
}
function reject(value){
if(that.state === PENDING){
that.state = REJECTED;
that.value = value;
that.rejectedCallbacks.map(cb=>cb(that.value))
}
}
try{
fn(resolve,reject)
}catch(e){
reject(e)
}
}
JPromise.prototype.then = function(onFulfilled,onRejected){
onFulfilled = typeof onFulfilled === 'function'?onFulfilled:v=>v;
onRejected = typeof onRejected === 'function'?onRejected:r=>new Error(r);
if(this.state === PENDING){
this.resolvedCallbacks.push(onFulfilled)
this.rejectedCallbacks.push(onRejected)
}
if(this.state ===RESOLVE){
onFulfilled(this.value);
}
if(this.state === REJECTED){
onRejected(this.value)
}
}
复制代码
而后运行一下:
new JPromise((resolve,rejected)=>{
resolve(1)
}).then(res=>{
console.log(res);
},error=>{
console.log(error);
})
复制代码
而后自测了一下,完美能实现简单的promise。可是跟大佬交流以后才知道:原来promise有本身的一套标准,我这套代码虽然可以简单的实现promise的功能 可是没有达到那套标准。
首先关于promise规范,我就简单的提一下吧:
最后张贴一下 我改进的代码:
const PENDING = 'pending';//初始态
const FULFILLED = 'fulfilled';//初始态
const REJECTED = 'rejected';//初始态
function Promise(executor){
let self = this;//先缓存当前promise实例
self.status = PENDING;//设置状态
//定义存放成功的回调的数组
self.onResolvedCallbacks = [];
//定义存放失败回调的数组
self.onRejectedCallbacks = [];
//当调用此方法的时候,若是promise状态为pending,的话能够转成成功态,若是已是成功态或者失败态了,则什么都不作
function resolve(value){ //2.1.1
if(value!=null &&value.then&&typeof value.then == 'function'){
return value.then(resolve,reject);
}
//若是是初始态,则转成成功态
//为何要把它用setTimeout包起来
setTimeout(function(){
if(self.status == PENDING){
self.status = FULFILLED;
self.value = value;//成功后会获得一个值,这个值不能改
//调用全部成功的回调
self.onResolvedCallbacks.forEach(cb=>cb(self.value));
}
})
}
function reject(reason){ //2.1.2
setTimeout(function(){
//若是是初始态,则转成失败态
if(self.status == PENDING){
self.status = REJECTED;
self.value = reason;//失败的缘由给了value
self.onRejectedCallbacks.forEach(cb=>cb(self.value));
}
});
}
try{
//由于此函数执行可能会异常,因此须要捕获,若是出错了,须要用错误 对象reject
executor(resolve,reject);
}catch(e){
//若是这函数执行失败了,则用失败的缘由reject这个promise
reject(e);
};
}
function resolvePromise(promise2,x,resolve,reject){
if(promise2 === x){
return reject(new TypeError('循环引用'));
}
let called = false;//promise2是否已经resolve 或reject了
if(x instanceof Promise){
if(x.status == PENDING){
x.then(function(y){
resolvePromise(promise2,y,resolve,reject);
},reject);
}else{
x.then(resolve,reject);
}
//x是一个thenable对象或函数,只要有then方法的对象,
}else if(x!= null &&((typeof x=='object')||(typeof x == 'function'))){
//当咱们的promise和别的promise进行交互,编写这段代码的时候尽可能的考虑兼容性,容许别人瞎写
try{
let then = x.then;
if(typeof then == 'function'){
//有些promise会同时执行成功和失败的回调
then.call(x,function(y){
//若是promise2已经成功或失败了,则不会再处理了
if(called)return;
called = true;
resolvePromise(promise2,y,resolve,reject)
},function(err){
if(called)return;
called = true;
reject(err);
});
}else{
//到此的话x不是一个thenable对象,那直接把它当成值resolve promise2就能够了
resolve(x);
}
}catch(e){
if(called)return;
called = true;
reject(e);
}
}else{
//若是X是一个普通 的值,则用x的值去resolve promise2
resolve(x);
}
}
//onFulfilled 是用来接收promise成功的值或者失败的缘由
Promise.prototype.then = function(onFulfilled,onRejected){
//若是成功和失败的回调没有传,则表示这个then没有任何逻辑,只会把值日后抛
//2.2.1
onFulfilled = typeof onFulfilled == 'function'?onFulfilled:function(value){return value};
onRejected = typeof onRejected == 'function'?onRejected:reason=>{throw reason};
//若是当前promise状态已是成功态了,onFulfilled直接取值
let self = this;
let promise2;
if(self.status == FULFILLED){
return promise2 = new Promise(function(resolve,reject){
setTimeout(function(){
try{
let x =onFulfilled(self.value);
//若是获取到了返回值x,会走解析promise的过程
resolvePromise(promise2,x,resolve,reject);
}catch(e){
//若是执行成功的回调过程当中出错了,用错误缘由把promise2 reject
reject(e);
}
})
});
}
if(self.status == REJECTED){
return promise2 = new Promise(function(resolve,reject){
setTimeout(function(){
try{
let x =onRejected(self.value);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
})
});
}
if(self.status == PENDING){
return promise2 = new Promise(function(resolve,reject){
self.onResolvedCallbacks.push(function(){
try{
let x =onFulfilled(self.value);
//若是获取到了返回值x,会走解析promise的过程
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
});
self.onRejectedCallbacks.push(function(){
try{
let x =onRejected(self.value);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
});
});
}
}
复制代码
具体的代码细节呢?我在代码中已经作了标注,若是又不懂的能够私聊、微信 欢迎来扰。
已经有好几个中午好怎么睡午觉了,这篇文章写的有点敷衍。原本下班的时候就8点半了,而后又整理/调试代码 整了半天,结果在写文章的时候想例子想个半天都想不出来。我明天会利用休息时间好好来修改一下这篇文章的,太困了 先这样吧,我洗澡去睡觉了。您若是对如今的这篇文章不太满意,就请过一天再来看。
最后提一句,能不能给我涨点人气啊,写了一年多,仍是这点人气..