GVKun编程网logo

测试时出现“不建议使用mpromise(Mongoose的默认promise库)”错误

14

本文将为您提供关于测试时出现“不建议使用mpromise的详细介绍,我们还将为您解释Mongoose的默认promise库”错误的相关知识,同时,我们还将为您提供关于11-利用Promise的图片异步

本文将为您提供关于测试时出现“不建议使用mpromise的详细介绍,我们还将为您解释Mongoose的默认promise库”错误的相关知识,同时,我们还将为您提供关于11-利用Promise的图片异步加载 / Promise封装ajax,模拟axios / Promise的finally原理、angularjs – chai-as-promise测试不适用于$q promises、ES6 Promise --回调与Promise的对比、信任问题、错误处理、Promise的状态、以及Promise对象的常用方法、ES6 Promise 源码解析 (从 Promise 功能的角度看 Promise 源码实现)的实用信息。

本文目录一览:

测试时出现“不建议使用mpromise(Mongoose的默认promise库)”错误

测试时出现“不建议使用mpromise(Mongoose的默认promise库)”错误

一段时间以来,我一直收到此错误,我决定今天修复它,但是一个小时后尝试修复它,我可以找到解决方案。

当我测试猫鼬用户模型时,会生成此错误/警告:

Mongoose: mpromise (mongoose''s default promise library) is deprecated, plug in your own promise library instead: http://mongoosejs.com/docs/promises.html

这是我的测试:

  1 var assert = require(''chai'').assert;  2 var mongoose = require(''mongoose'');  3 var clearDB = require(''mocha-mongoose'')(require(''../../config/database'').uri, { skip: [''workouts''] });  4 var database = require(''../../config/database'').connect;  5  6 var User = require(''../../app/models/user'');  7 var user = new User({});  8  9 var req_body = { 10   username: "garyvee", 11   email: "gary@vaynermedia.com", 12   password: "secret" 13 }; 14 15 describe(''User'', function() { 16   beforeEach(function(done) { 17     user.username = "johnsmith"; 18     user.email = "john@gmail.com"; 19     user.password = "secret"; 20     done(); 21   }); 22 23   it(''can be saved'', function() { 24     return user.save(function(err: any) { 25       assert.isNull(err); 26     }) 27   }); 28 });

我认为它与无关,.save但我不知道如何解决。有人可以帮助我,告诉我如何解决它,以便不显示错误/警告。

答案1

小编典典

您需要插入一个Promise库(q,bluebird,es6一个…)

mongoose.Promise = require(''bluebird'');

11-利用Promise的图片异步加载 / Promise封装ajax,模拟axios / Promise的finally原理

11-利用Promise的图片异步加载 / Promise封装ajax,模拟axios / Promise的finally原理

Promise的图片异步加载其实就是利用了宏任务先执行,后执行微任务:

new Promise()的时候,Promise新建后就会立即执行

 

 利用这一特性,我们可以创建Promise对象的时候,创建image标签,然后再给img标签的 src赋值路径,这样在then的回调函数中,把其加入到盛放显示图片的盒子中,盒子中原来展示是一个缺省图,等到图片加载好了,就显示真正的图片:

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 
 4 <head>
 5     <Meta charset="UTF-8">
 6     <Meta http-equiv="X-UA-Compatible" content="IE=edge">
 7     <Meta name="viewport" content="width=device-width, initial-scale=1.0">
 8     <title>Document</title>
 9 </head>
10 <style>
11     h1 {
12         display: block;
13     }
14 </style>
15 
16 <body>
17     <div id='Box'>
18         <h1>我是一张缺省图</h1>
19     </div>
20 </body>
21 
22 </html>
23 <style>
24 
25 </style>
26 <script>
27     var oBox = document.getElementById('Box');
28     var oH = document.querySelector('h1')
29 
30     function loadImageAsync(url) {
31         return new Promise(function(resolve, reject) {
32             var image = new Image();
33 
34             image.onload = function() {
35                 resolve(image);
36             };
37 
38             image.onerror = function() {
39                 reject(new Error('Could not load image at ' + url));
40             };
41 
42             image.src = url;
43         });
44     }
45     // 模拟一下异步加载图片
46     // 用setTimeoutm模拟ajax调用接口,获取接口返回的图片路径,然后传入函数中,函数中已经提前创建好了
47     // 图片标签。我们在.then的回调函数中自行决定插入div容器中做一些事,比如把缺省图隐藏掉
48     setTimeout(() => {
49         loadImageAsync('./lion.jpg').then(res => {
50             oH.style.display = 'none';
51             oBox.appendChild(res);
52         })
53     }, 1000)
54 </script>

 

 1秒后显示图片:


 

 Promise封装ajax

 

 1    function myAxios(url) {
 2         return new Promise((resolve, reject) => {
 3             let http = new XMLHttpRequest();
 4             http.open('GET', url)
 5             http.onreadystatechange = function() {
 6                 if (this.readyState !== 4) {
 7                     return
 8                 }
 9                 if (this.status === 200) {
10                     resolve(this.response)
11                 } else {
12                     reject(new Error(this.txt))
13                 }
14             }
15             http.send();
16         })
17     }
18 
19     myAxios('').then(res => {
20         // 拿到数据
21     }).catch(err => {
22         // 捕获错误
23     })

Promise的finally原理

 

 

 

angularjs – chai-as-promise测试不适用于$q promises

angularjs – chai-as-promise测试不适用于$q promises

我正在努力让chai-as-promised与$q承诺一起使用karma单元测试.
svc.test = function(foo){
    if (!foo){
      // return Promise.reject(new Error('foo is required'));
      return $q.reject(new Error('foo is required'));
    } else {
      // get data via ajax here
      return $q.resolve({});
    }
  };


  it.only('should error on no foo',function(){
    var resolvedValue = MyServices.test();
    $rootScope.$apply();
    return resolvedValue.should.eventually.be.rejectedWith(TypeError,'foo is required');
  });

单元测试只是超时了.我不确定我在这里做错了什么来得到正确解决的承诺.使用$q似乎是一个问题 – 当我使用本机Promise.reject()时,它工作正常.

我在这里提交了一张票,但似乎没有人回应:
https://github.com/domenic/chai-as-promised/issues/150

您需要更改测试中的执行顺序.具有chai-as-promise的异步任务需要在预期之前发生.
it('does not work',() => {
  $timeout.flush();
  expect(myAsyncTask()).to.eventually.become('foo');
})

it('does work',() => {
  expect(myAsyncTask()).to.eventually.become('foo');
  $timeout.flush();      
})

在刷新异步任务队列之前,需要启动对异步任务的调用.

另外,不要使用$rootScope.$digest.这可能会产生其他副作用,这些副作用在您的测试中是不可取的.

$timeout.flush是你正在寻找的.

https://docs.angularjs.org/api/ngMock/service/ $超时

让您的特定测试工作:

it('should error on no foo',function(){
  MyServices.test().should.eventually.be.rejectedWith(TypeError,'foo is required')
  $rootScope.$apply();
});

it('should pass on foo',function(){
  MyServices.test('foo').should.eventually.become({});
  $rootScope.$apply();      
}

TL;博士

it('async test',() => {
  setup();
  expect();
  execute();
})

it('sync test',() => {
  setup();
  execute();
  expect();
})

鉴于发表的评论:

Should it be mentioned that it is unethical to downVote ‘rival’ answers on the question you’re answering?

很公平.我认为答案是误导性的,因为没有必要进行额外的设置以使得使用Angular的承诺可以在不必处理完成的回调的情况下使用它. Fwiw,我会继续尝试撤销所说的downVote,并对此有道德.

The OP has no signs of timeout in his code and doesn’t state that the task is asynchronous. $rootScope.$digest() has no side effects in specs when called outside of scope digest. The reason why it is not recommended in production is because it doesn’t have the safeguards that $apply has.

$rootScope.$digest实际上与$rootScope相同.$apply(和$scope.$适用于此事). source

$timeout.flush也将刷新非基于$timeout的函数.它不是基于$timeout的功能所独有的.

Plunker展示它如何工作™:
plunker

ES6 Promise --回调与Promise的对比、信任问题、错误处理、Promise的状态、以及Promise对象的常用方法

ES6 Promise --回调与Promise的对比、信任问题、错误处理、Promise的状态、以及Promise对象的常用方法

之前怎么用回调解决异步的问题:

function f(callback){
    setTimeout((){
        callback && callback();
    });    
}

f((){
    console.log(1);
    f((){
        console.log(2);    
        f((){
            console.log(3);    
            f((){
                console.log(4);    
                f((){
                    console.log(5);    
                    f((){
                        console.log(6);        
                    })    
                })        
            })    
        })    
    })
})

 

使用promise实现相同的效果

//使用promise实现相同的效果
 f2(){
    return new Promise(resolve=>{参数传入一个回调函数
        setTimeout((){
            时执行函数
            resolve();
        },1000)
    })
}

f2()只有返回Promise实例,才能.then
.then((){
    console.log(11);
    return f2();
})
.then((){
    console.log(22(){
    console.log(33(){
    console.log(44(){
    console.log(55(){
    console.log(66 f2();
})

 

 

对比回调与Promise的流程控制

首先是回调

<!DOCTYPE html>
<html lang="en">
<head>
    <Meta charset="UTF-8">
    <title>index</title>
    <style>
        .Box{
            width:100px;
            height:100px;
            background:lightgreen;
            transition:all 1s;
            color:#fff;
            text-align:center;
            line-height:100px;
            font-size:40px;
        }
    </style>
</head>
<body>

    <divhttps://www.jb51.cc/tag/Box/" target="_blank">Box">哦</div>
    <button id="btn">开始</button>
<script>
动画
 move(el,x,y,cb){
    el.style.transform=`translate(${ x }px,${ y }px)`;
    setTimeout((){
        cb && cb();
    },1)">);    
}

获取元素
let Box=document.querySelector(".Box");
let btn=document.querySelector("#btn");
绑定事件
btn.addEventListener("click",e=>{
    使用回调完成动画
    move(Box,100,(){
        move(Box,200,200,1)">(){
            move(Box,100,300,1)">(){
                move(Box,0,1)">(){
                    console.log("移动结束!");
                })
            })    
        })    
    })    
})

</script>
</body>
</html>

实现的效果

 

使用Promise来实现

<!DOCTYPE html>
<html lang="en">
<head>
    <Meta charset="UTF-8">
    <title>index</title>
    <style>new Promise(resolve=>{
        el.style.transform=(){
            resolve();
        },1)">);
    })        
}

使用Promise完成动画
    move(Box,100)
    .then((){        
        return move(Box,200);
    })
    .then((){
        console.log("移动结束!");
    })
})

</script>
</body>
</html>

 

实现一个图片的加载;设置第一张图片加载1s之后加载第二张图片

<!DOCTYPE html>
<html lang="en">
<head>
    <Meta charset="UTF-8">
    <title>index</title>
    <style>
        img{width:200px;}
    </style>
</head>
<body>


<script>

设置一个函数,把图片的URL地址作为参数
 createImg(url){
    实例化promise对象
    {
        let img=new Image();建立图像对象
        img.src=url;设置图片的地址
        document.body.appendChild(img);把图片节点插入到body中
        setTimeout((){
            resolve();图片加载完成后执行resolve 
        },1000);
    })
}

createImg("1.jpg")
.then((){
    return createImg("2.jpg")
})
.then(return createImg("3.jpg")
});

</script>
</body>
</html>

信任问题

信任问题演示

回调
 method(cb){
    setTimeout( cb();
        因为某些bug导致某个函数多执行了一次
        cb &&);
}

promise
 method2(){
    {
        setTimeout((){
            resolve();
            resolve成功调用一次之后,后面的不会再执行
);        
    })
}

 

控制反转

(){
        cb && cb.call({a:1,b:2});执行回调,但是添油加醋
    },1)">调用的resolve都是自己写的,改善了控制反转的问题
        },1)">);        
    })
}

 

错误处理

 fn(val){
    第二个参数代表失败时做的事情
    new Promise((resolve,reject)=>{
        if(val){
            resolve();
        }else{
            reject();
        }
    })
}

fn(false)
.then(()=>{
    console.log("成功");
},()=>{
    console.log("失败");
})

 

 

错误处理回调可以传入参数

{
            reject("404");
        }
    })
}

fn({
    console.log(e);
})

 

 

resolve也可以传递参数,但是只能传一个,不能传两个

(val){
            resolve({a:1},{b:2});
        }true)
.then((obj1,obj2)=>{
    console.log(obj1);
    console.log(obj2);
},1)">{
    console.log(e);
})

 

 

使用实例的catch方法,可以捕获错误

如果返回的是错误,则下面必须有对错误的捕获处理,否则代码不会执行,会被跳过

(val){
            resolve("这是数据");
        })
.then(data=>{
    console.log(data);
    return fn(false);失败,抛出错误
})
.then(()=>{
    console.log("这里没有对错误的处理,因此不会执行");
})
.catch(e=>{捕获错误,执行代码
    console.log(e);
})

 

 

如果在捕获错误之前,存在对错误的处理,那么catch不会再执行

);
})
.then(()=>{
    
},1)">{
    console.log("这里对错误进行了处理,下面的catch不会被执行了"    console.log(e);
})

 

 

catch之后还可以继续then,如果再次抛出错误,也需要在之后进行错误处理

    console.log(e);
    再次抛出错误
});

最后抛出的错误没有捕获,因此报错

 

 

 

finally 不管成功或失败,都会执行

})
.    console.log(e);
})
.finally(()=>{
    console.log("finally执行一些收尾工作");
})

 

Promise的状态

panding 进行中

fulfilled 成功

reject 失败

 

 

Promise.all()

把多个promise实例,包装成一个新的promise实例

如果所有promise结果都是成功,则返回成功,所有promise返回的数据以数组形式统一返回,且顺序一一对应

如果有一个promise决议为失败,则返回失败,且把失败的信息返回

如果是空数组,则立即决议为成功

模拟需要多个请求数据才能进行下一步的情况
 data1(){
    {
        setTimeout(()=>{
            console.log("data1加载成功");
            resolve("data1");传递参数
        },1)">)
    })
}

 data2(){
    {
            console.log("data2加载成功");
            resolve("data2");)
    })
}    

 data3(){
    {
            console.log("data3加载成功");
            resolve("data3"); data4(){
    {
            console.log("data4加载成功");
            resolve("data4");全部成功的情况
let res=Promise.all([data1(),data2(),data3(),data4()]);
res
.then(data=>{
    console.log(data);接收上面传递过来的所有参数
})

 

 

{
            reject("data2 err");数据2请求失败
        },1)">接收上面传递过来的所有参数
},1)">{
    console.log(e);有错误执行这句并立刻返回错误信息,正确的数据不会返回
})

 

 

Promise.all([]);
res
.then(()=>{
    console.log("决议成功");空数组直接决议为成功
},1)">有错误执行这句并立刻返回错误信息,正确的数据不会返回
})

 

 

相同的功能,使用ES6之前的语法,不使用promise.all()要如何实现:

不使用promise.all()

let count=0;

 fn(){
    if(count<4) ;
    console.log("数据全部接收成功" data1(){
    setTimeout(()=>{            
        console.log("data1加载成功");
        count++;
        fn();
    },2000)
}

 data2(){
    setTimeout(()=>{            
        console.log("data2加载成功")
}    

 data3(){
    setTimeout(()=>{            
        console.log("data3加载成功" data4(){
    setTimeout(()=>{            
        console.log("data4加载成功")
}    

data1();
data2();
data3();
data4();

 

 

如果有数据接收失败

;
let err=(err) {
        console.log("有数据接收失败");
        ;
    }
    {            
        console.log("data2加载失败");
        err=)
}    

data1();
data2();
data3();
data4();

 

 

Promise.race()

数组中只要有一个决议为成功,则立马决议为成功,并把值传递过来

promise.race()
{    
            console.log("data1成功")    ;    
            resolve("data1");
        },1)">{    
            console.log("data2成功")    ;            
            resolve("data2"{
            console.log("data3成功")    ;                
            resolve("data3"{    
            console.log("data4成功")    ;            
            resolve("data4")
    })
}    

let res=Promise.race([data1(),1)">输出最早成功的数据
},1)">{
    console.log(e);
})

 

 

如果有错误,也会立即输出err信息

{    
            console.log("data2失败")    ;            
            reject("err2"{    
            console.log("data4失败")    ;            
            resolve("err4"{
    console.log(e);
})

 

 

如果传入空数组,则程序被挂起

Promise.race([]);
res
.then(data=>{
    console.log(e);
})

 

 

如果不使用ES6的promise.race(),实现效果如下

不使用promise.race()
let flag=;
 fn(data){
    if(flag) ;
    flag=true;有请求则返回true
    console.log(data);
}

{    
        console.log("data1成功")    ;    
        fn({name:1})
    },1)">{    
        console.log("data2成功")    ;
        fn({name:2})    
    },600{    
        console.log("data3成功")    ;
        fn({name:3{    
        console.log("data4成功")    ;    
        fn({name:4)
}    

data1();
data2();
data3();
data4();

 

 

Promise.resolve() 不管传递什么值进去,都会包装成一个promise实例

promise.resolve()

传递一个普通值
let p1={
    console.log("p1决议成功");
})

let p2=Promise.resolve("p2成功");传递一个普通的值,直接决议为成功

传递一个promise实例
let p11={
    console.log("p11决议成功");
})

let p22=Promise.resolve(p11);传递一个promise实例,使得p11和p22相等
p11.then(data=>void console.log(data));
console.log(p11===p22);


定义一个thenable对象obj
let obj={
    then(cb){
        console.log("成功")
        cb("成功啦")
    },oth(){
        console.log("失败")
    }
}
Promise.resolve(obj) 传递一个thenable对象
Promise.resolve(obj).then(data=>{
    console.log(data)
})

 

 

 

Promise.reject()

不管传递什么值,拿到的都是传入的值,不会进行操作和处理

promise.reject()

传递一个thenable对象obj
Promise.reject({then(){console.log("err")}})
.then((){
    console.log("我不会被执行"{
    console.log(e)
})

 

 

resolve是异步任务,会在所有同步任务完成后执行

console.log(1);

let p={
    console.log(2);
    resolve();调用resolve相等于调用.then,是异步执行,在所有同步完成后执行
    console.log(3);
})

console.log(4);

p.then(()=>{
    console.log(5);
})

console.log(6);

 

 

把同步任务转为异步任务

 fn(cb){
    返回一个决议成功的实例,并异步执行
    return Promise.resolve(cb).then(cb=>cb());
}

fn(()=>{
    console.log("我从同步变成了异步"return 1+1;
}).then(res=>{
    console.log(res);拿到return的值
})

console.log("我是同步");

 

 

小案例

页面中有多个板块,需要所有图片加载完成后再显示

<!DOCTYPE html>
<html lang="en"head>
    Meta charset="UTF-8"title>es6 promise</style>
    img{height:100px;}
bodyscript



const loadImg=(src)=>{
    return new Promise((resolve,reject){
        let img Image();
        img.srcsrc;
        //图片加载成功
        img.onload(){
            resolve(img)
        }
        图片加载失败
        img.onerror(e){
            reject(e)
        }
        注意这种写法是错误的,因为赋值时直接被调用,还没有等待图片加载已经执行完毕了
         img.onload=resolve(img)
         img.onerror=reject(e)
    })
}

const imgs["1.jpg,2.jpg3.jpg];
 map通过遍历把src作为参数传入,循环调用loadImg,获取到返回的image对象
Promise.all(imgs.map(srcloadImg(src)))
.then(res{
    console.log(res);
    遍历插入DOM
    res.forEach(item{
        document.body.appendChild(item)
    })
})

html>

 

 

 

失败的情况

<!DOCTYPE html>
<html lang="en">
<head>
    <Meta charset="UTF-8">
    <title>es6 promise</title>
</head>
<style>
    img{height:100px;}
</style>
<body>
<script>



const loadImg=(src)=>new Image();
        img.src=src;
        图片加载成功
        img.onload=()=>{
            resolve(img)
        }
        图片加载失败
        img.onerror=(e)=>{
            reject(e)
        }
        注意这种写法是错误的,因为赋值时直接被调用,还没有等待图片加载已经执行完毕了
         img.onload=resolve(img)
         img.onerror=reject(e)
    })
}

const imgs=["1.jpg","22.jpg","3.jpg"];
 map通过遍历把src作为参数传入,循环调用loadImg,获取到返回的image对象
Promise.all(imgs.map(src=>loadImg(src)))
.then(res=>{
    console.log(res);
    遍历插入DOM
    res.forEach(item=>{
        document.body.appendChild(item)
    })
})
.catch(e=>{
    console.log(e)
})

</script>
</body>
</html>

 

总结

以上是小编为你收集整理的ES6 Promise --回调与Promise的对比、信任问题、错误处理、Promise的状态、以及Promise对象的常用方法全部内容。

如果觉得小编网站内容还不错,欢迎将小编网站推荐给好友。

ES6 Promise 源码解析 (从 Promise 功能的角度看 Promise 源码实现)

ES6 Promise 源码解析 (从 Promise 功能的角度看 Promise 源码实现)

上一篇,我们说了 Promise 的用法详解。今天趁周末有空,我们来继续看一看 Promise 源码。下面,我将从 Promise 功能的角度一步一步来看每步的功能是怎么实现的

首先,我们再来回顾一下 Promise 的基本用法,看代码

new Promise((resolve, reject) => {
   
   
    try {
   
   
        if (success) {
   
   
            resolve()
        }
        if (fail) {
   
   
            reject()
        }
    } catch (err) {
   
   
        reject(err)
    }
}).then((res) => {
   
   
    console.log(res)
}, (err) => {
   
   
    console.log(err)
}).then((res) => {
   
   
    console.log(res)
}, (err) => {
   
   
    console.log(err)
}).catch((err => {
   
   
    console.log(err)
}))

我们先从上面的用法上,总结出一些 Promise 的特点

  1. 首先,毫无疑问,Promise () 是一个构造函数,并且,存在 3 种状态,pending, fulfilled (也可以叫 Resolved), rejected。分别表示等待时,成功时,失败时
  2. Promise 实例化时,传了个参数,并且这个参数是个函数(并且是个立即执行函数),同时这个函数还有两个参数,且这两个参数,依然是函数,分别是 resolve (), reject ()
  3. then () 函数中的第一个参数(后文我们统称为 then 的 resolve 回调),是在调用 then () 方法的 Promise 对象的状态变为 fulfilled 时被执行的,而第二个参数(后文我们统称为 rejected 回调),是在 Promise 对象的状态变成 rejected 时被调用的。
  4. 通过第四代,我们还可以看出,在 resolve () 函数执行时,是将 Promise 对象的状态变更成了 fulfilled, 从而触发了 then 的 resolve 回调函数的执行。而 reject () 函数执行时,是将 Promise 对象的状态变更成了 rejected,从而触发了 then 的 reject 回调的执行
  5. resolve (res) 时的值,就是 then 的 resolve 回调的参数,reject (err) 的值就是 thenreject 回调的参数
  6. then () 函数和 catch () 函数可以被链式调用

1、Promise 基础雏形
2、then () 函数的完善
3、Promise.then () 的链式调用
4、Promise.catch()
5、Promise.resolve()
6、Promise.reject()
7、Promise.all()
8、Promise.race()






Promise 基础雏形

  1. 知晓了他是个构造函数,那我们就创建个构造函数,并定义好状态,并立即执行实例化时传入的函数
class MyPromise {
   
   
	constructor (fun) {
   
   
		 // 定义初始状态(3个状态分别是pending, fulfilled, rejected)
        this.status = ''pending''
        // 定义两个变量分别来存储成功时值和失败时的值
        this.resolveValue = null
        this.rejectValue = null

		// 定义resolve函数
		let resolve = () => {
   
   }
		
		// 定义reject函数
		let reject = () => {
   
   }
		try {
   
   
			fun(resolve, reject)
		} catch (err) {
   
   
			reject(err)
		}
	}
}

此时,构造函数最基本的样子已经有了,定义了一个状态,执行了立即执行函数。并将 resolve, reject 传入到立即执行函数中。下面我们来完善下 resolve, reject

let resolve = (val) => {
   
   
	// 1、将状态变更为fulfilled, 但是注意一点,Promise是有个特点的,就是状态只能由pending状态变更为fulfilled或者由pending状态变更为rejected。且,状态变化后,不会再变化。故,我们需要先判断当前是否是等待状态pending
	if (this.status === ''pending'') {
   
     // this指向实例化出来Promise对象
		this.status = ''fulfilled''
		// 2、保存resolve时的值,以便后面调用then()方法时使用
		this.resolveValue = val
	}
}

let reject = (val) => {
   
   
	// 1、状态变更为rejected
	if (this.status === ''pending'') {
   
   
		this.status = ''rejected''
		// 2、保存reject()时的值
		this.rejectValue = val
	}
}

此时,resolve, reject 已经有了,new Promise () 时,已经可以调用 resolve () 方法和 reject () 方法了。并且,resolve () 和 reject () 时,promise 状态也已经发生了改变。并保存了 resolve 和 reject 出来的值。

  1. 在下一步,状态已经发生改变了,我们是不是要触发 then 的 resolve 回调,或者 reject 回调了。所以,我们来实现 then () 函数
MyPromise.prototype.then = (onFullFilled, onRejected) => {
   
   
	// onFullFilled, onRejected分别resolve()时的回调函数和reject()时的回调函数
	// 此时,判断状态,不同状态时,分别执行不同的回调
	if (this.status === ''fulfilled'') {
   
   
		onFullFilled(this.resolveValue)
	}
	if (this.status === ''rejected'') {
   
   
		onRejected(this.rejectValue )
	}
	// 上面的this指向的是调用then的promise实例,故可以直接拿到状态和返回值
}

此时,我们一条执行流程应该是走完了,下面,我们来测试下

class MyPromise {
   
   
    constructor (fun) {
   
   
		// 定义初始状态(3个状态分别是pending, fulfilled, rejected)
	    this.status = ''pending''
	    // 定义两个变量分别来存储成功时值和失败时的值
	    this.resolveValue = null
	    this.rejectValue = null

		// 定义resolve函数
	    let resolve = (val) => {
   
   
	        // 1、将状态变更为fulfilled, 但是注意一点,Promise是有个特点的,就是状态只能由pending状态变更为fulfilled或者由pending状态变更为rejected。且,状态变化后,不会再变化。故,我们需要先判断当前是否是等待状态pending
	        if (this.status === ''pending'') {
   
     // this指向实例化出来Promise对象
	            this.status = ''fulfilled''
	            // 2、保存resolve时的值,以便后面调用then()方法时使用
	            this.resolveValue = val
	        }
	    }
	    
	    // 定义reject函数
	    let reject = (val) => {
   
   
	        // 1、状态变更为rejected
	        if (this.status === ''pending'') {
   
   
	            this.status = ''rejected''
	            // 2、保存reject()时的值
	            this.rejectValue = val
	        }
	    }
	
	    try {
   
   
	        fun(resolve, reject)
	    } catch (err) {
   
   
	        reject(err)
	    }
	}
}

MyPromise.prototype.then = function (onFullFilled, onRejected) {
   
   
    // onFulfilled, onRejected分别resolve()时的回调函数和reject()时的回调函数
	// 此时,判断状态,不同状态时,分别执行不同的回调
	if (this.status === ''fulfilled'') {
   
   
		onFullFilled(this.resolveValue)
	}
	if (this.status === ''rejected'') {
   
   
		onRejected(this.rejectValue )
	}
	// 上面的this指向的是调用then的promise实例,故可以直接拿到状态和返回值
}

const promise1 = new MyPromise((resolve, reject) => {
   
   
    resolve(123)
})
promise1.then((res) => {
   
   
    console.log(res)  // 123
}, (err) => {
   
   
    console.log(err)
})

输出了123

then () 函数的完善

到此时,只是完成了基本,但仍存在很多问题。大家发现没有,我们在 then 函数中,只判断了状态为 fufilled 时,调了 onFullFilled,状态为 rejected 时,调了 onRejected。但如果 then () 函数被调用时,promise 的状态还并未发生改变(也就是还处于 pending 时),那 then () 函数内的代码是不是不会执行拉。因为我们并没有写 pending 状态时的处理代码。如以下情况

const promise1 = new MyPromise((resolve, reject) => {
   
   
    setTimeout(() => {
   
   
        resolve(123)
    }, 0)
})
promise1.then((res) => {
   
   
    console.log(res)
}, (err) => {
   
   
    console.log(err)
})
没有输出

上面的代码中,立即执行函数内是一个异步代码,此时,是不是先执行了 promise1.then (),然后才执行 setTimeout 中的回调函数啊。所以,此时,当 promise1.then () 执行时,状态已经是 pending,故不会有任何输出。这就有问题了。而我们所希望的,是不是在 resolve () 或者 reject () 执行的时候,去触发 then () 函数的 resolve 回调或者 rejected 回调执行啊。
那该怎么做。我先说下思路。在 then 函数执行时,如果状态还是 pending 的话,我们是不是可以先把 then () 的两个回调函数先给他保存起来。然后在 resolve () 或者 reject () 的时候,再去触发这个函数的调用呢。我们来写一下
1、先定义两个变量用来保存 then () 的回调函数

this.onFullFilledList = []
this.onRejectedList = []

2、then () 执行时,如果状态还未发生改变(还是 pending 时),那么就将回调函数先保存起来

MyPromise.prototype.then = function (onFullFilled, onRejected) {
   
   
    // onFulfilled, onRejected分别resolve()时的回调函数和reject()时的回调函数
	// 此时,判断状态,不同状态时,分别执行不同的回调
	if (this.status === ''fulfilled'') {
   
   
		onFullFilled(this.resolveValue)
	}
	if (this.status === ''rejected'') {
   
   
		onRejected(this.rejectValue )
    }
    if (this.status === ''pending'') {
   
   
    	// 保存的是一个函数,而函数内是回调的执行代码,当我们执行被保存的函数时,函数内的onFullFilled和onRejected是不是也就跟着执行拉
        this.onFullFilledList.push(() => {
   
   
            onFullFilled(this.resolveValue)
        })
        this.onRejectedList.push(() => {
   
   
            onRejected(this.rejectValue )
        })
    }
	// 上面的this指向的是调用then的promise实例,故可以直接拿到状态和返回值
}

3、在 resolve () 和 reject () 的时候,去取 onFullFilledList,onRejectedList 两个队列中的函数,并依次执行

// 定义resolve函数
 let resolve = (val) => {
   
   
	 // 1、将状态变更为fulfilled, 但是注意一点,Promise是有个特点的,就是状态只能由pending状态变更为fulfilled或者由pending状态变更为rejected。且,状态变化后,不会再变化。故,我们需要先判断当前是否是等待状态pending
     if (this.status === ''pending'') {
   
     // this指向实例化出来Promise对象
         this.status = ''fulfilled''
         // 2、保存resolve时的值,以便后面调用then()方法时使用
         this.resolveValue = val

         // 执行then的resolve回调
         this.onFullFilledList.forEach(funItem => funItem())
     }
 }
 
 // 定义reject函数
 let reject = (val) => {
   
   
     // 1、状态变更为rejected
     if (this.status === ''pending'') {
   
   
         this.status = ''rejected''
         // 2、保存reject()时的值
         this.rejectValue = val

         // 执行then的reject回调
         this.onRejectedList.forEach(funItem => funItem())
     }
 }

到此,就不会再有因为异步代码而执行不了的问题了。看下完整代码,并验证下

class MyPromise {
   
   
    constructor(fun) {
   
   
		// 定义初始状态(3个状态分别是pending, fulfilled, rejected)
	    this.status = ''pending''
	    // 定义两个变量分别来存储成功时值和失败时的值
	    this.resolveValue = null
	    this.rejectValue = null
	    this.onFullFilledList = []
	    this.onRejectedList = []

		// 定义resolve函数
	    let resolve = (val) => {
   
   
	        // 1、将状态变更为fulfilled, 但是注意一点,Promise是有个特点的,就是状态只能由pending状态变更为fulfilled或者由pending状态变更为rejected。且,状态变化后,不会再变化。故,我们需要先判断当前是否是等待状态pending
	        if (this.status === ''pending'') {
   
     // this指向实例化出来Promise对象
	            this.status = ''fulfilled''
	            // 2、保存resolve时的值,以便后面调用then()方法时使用
	            this.resolveValue = val
	
	            // 执行then的resolve回调
	            this.onFullFilledList.forEach(funItem => funItem())
	        }
	    }
	    
	    // 定义reject函数
	    let reject = (val) => {
   
   
	        // 1、状态变更为rejected
	        if (this.status === ''pending'') {
   
   
	            this.status = ''rejected''
	            // 2、保存reject()时的值
	            this.rejectValue = val
	
	            // 执行then的reject回调
	            this.onRejectedList.forEach(funItem => funItem())
	        }
	    }
	
	    try {
   
   
	        fun(resolve, reject)
	    } catch (err) {
   
   
	        reject(err)
	    }

	}
}

MyPromise.prototype.then = function (onFullFilled, onRejected) {
   
   
    // onFulfilled, onRejected分别resolve()时的回调函数和reject()时的回调函数
	// 此时,判断状态,不同状态时,分别执行不同的回调
	if (this.status === ''fulfilled'') {
   
   
		onFullFilled(this.resolveValue)
	}
	if (this.status === ''rejected'') {
   
   
		onRejected(this.rejectValue )
    }
    if (this.status === ''pending'') {
   
   
        this.onFullFilledList.push(() => {
   
   
            onFullFilled(this.resolveValue)
        })
        this.onRejectedList.push(() => {
   
   
            onRejected(this.rejectValue )
        })
    }
	// 上面的this指向的是调用then的promise实例,故可以直接拿到状态和返回值
}

const promise1 = new MyPromise((resolve, reject) => {
   
   
    setTimeout(() => {
   
   
        resolve(123)
    }, 0)
})
promise1.then((res) => {
   
   
    console.log(res)  // 123
}, (err) => {
   
   
    console.log(err)
})
输出了123

Promise.then () 的链式调用

下面我们来说下 then 的链式调用

promise1.then((res) => {
   
   
	console.log(res)
}, (err) => {
   
   
	console.log(err)
}).then((res) => {
   
   
	console.log(res)
}, (err) => {
   
   
	console.log(err)
})

上一篇文章里面我就说过,Promise 之所以能够进行链式调用,是因为 then () 方法内部返回了一个 Promise 实例,而返回的这个 Promise 实例在继续调用了第二个 then () 方法。并且第二个 then 的 resolve 回调的参数,是上一个 then 的 resolve 回调函数的返回值。

new Promise((resolve, reject) => {
   
   
	resolve(123)
}).then((res) => {
   
   
	console.log(res)
	return 456
}).then((res) => {
   
   
	console.log(res)
	return 789
}).then((res) => {
   
   
	console.log(res)
})
依次输出123   456   789

那么,根据我们所看到的功能,我们来改造下 then

MyPromise.prototype.then = function (onFullFilled, onRejected) {
   
   
	// 将then函数内部返回的Promise对象取名为promise2,后续文档中将直接以promise2来表示这个对象
    const promise2 = new MyPromise((resolve, reject) => {
   
   
        // onFulfilled, onRejected分别resolve()时的回调函数和reject()时的回调函数
        // 此时,判断状态,不同状态时,分别执行不同的回调
        if (this.status === ''fulfilled'') {
   
   
            // 定义一个变量来保存onFullFilled的返回值
            let result = onFullFilled(this.resolveValue)
            resolve(result)
        }
        if (this.status === ''rejected'') {
   
   
            // 定义一个变量来保存onRejected的返回值
            let result = onRejected(this.rejectValue )
            reject(result)
        }
        if (this.status === ''pending'') {
   
   
            this.onFullFilledList.push(() => {
   
   
                let result = onFullFilled(this.resolveValue)
                resolve(result)
            })
            this.onRejectedList.push(() => {
   
   
                let result = onRejected(this.rejectValue )
                resolve(result)
            })
        }
    })
}

可以看到,我们在调用 then 时,返回了一个新的 Promise 实例,并且将这个 then (onFullFilled,onRejected) 的 resolve 回调和 reject 回调的返回值 resolve 或者 reject 出去了。
这种方式,对于当 onFullFilled 返回的是一个普通值来说,是可行的,但如果 onFullFilled 返回的是一个 Promise 对象或者函数呢。
从原生 Promise 的功能上,我们是可以看出的

  1. 当 onFullFilled 函数返回值是普通值时,下一个 then 的 onFullFilled 函数将会以这个返回值作为参数。
  2. 当 onFullFilled 函数返回值是一个函数时,下一个 then 的 onFullFilled 函数也会直接以这个函数当作参数
  3. 当 onFullFilled 函数返回值是一个 Promise 时,then () 方法返回的 Promise 对象 (下文统称为 promise2) 的状态就取决去这个 onFullFilled 函数所返回的 Promise 的状态。promise2 resolve () 或者 reject () 的值,取决于 onFullFilled 函数返回的 Prmise 对象 resolve () 或者 reject () 的值
    所以,此时,我们是不是需要一个函数来专门判断这个 onFullFilled 的返回值到底是普通值还是函数还是 Promise 对象。并且,当值不同时,处理方式就不一样。
    如下:

MyPromise.prototype.then = function (onFullFilled, onRejected) {
   
   
	// 将then函数内部返回的Promise对象取名为promise2,后续文档中将直接以promise2来表示这个对象
    const promise2 = new MyPromise((resolve, reject) => {
   
   
        // onFulfilled, onRejected分别resolve()时的回调函数和reject()时的回调函数
        // 此时,判断状态,不同状态时,分别执行不同的回调
        if (this.status === ''fulfilled'') {
   
   
        	setTimeout(() => {
   
   
				try {
   
   
					// 定义一个变量来保存onFullFilled的返回值
		            let result = onFullFilled(this.resolveValue)
		            formatPromise(promise2, result, resolve, reject)
				} catch (err) {
   
   
					reject(err) // 捕捉上面代码执行的错误
				}
			}, 0) // 这里说明下为说明要用setTimeout, 因为我这段代码要用到promise2,而如果是同步代码,promise2不可在自己的立即执行函数内调用自己
        }
        if (this.status === ''rejected'') {
   
   
        	setTimeout(() => {
   
   
        		try {
   
   
					// 定义一个变量来保存onRejected的返回值
            		let result = onRejected(this.rejectValue )
		            formatPromise(promise2, result, resolve, reject)
				} catch (err) {
   
   
					reject(err) // 捕捉上面代码执行的错误
				}
			}, 0)
        }
        if (this.status === ''pending'') {
   
   
            this.onFullFilledList.push(() => {
   
   
                setTimeout(() => {
   
   
					try {
   
   
						// 定义一个变量来保存onFullFilled的返回值
			            let result = onFullFilled(this.resolveValue)
			            formatPromise(promise2, result, resolve, reject)
					} catch (err) {
   
   
						reject(err) // 捕捉上面代码执行的错误
					}
				}, 0)
            })
            this.onRejectedList.push(() => {
   
   
                setTimeout(() => {
   
   
	        		try {
   
   
						// 定义一个变量来保存onRejected的返回值
	            		let result = onRejected(this.rejectValue )
			            formatPromise(promise2, result, resolve, reject)
					} catch (err) {
   
   
						reject(err) // 捕捉上面代码执行的错误
					}
				}, 0)
            })
        }
    })
    return promise2
}

下面我们再来写一下 formatPromise ()

const formatPromise = (promise, result, resolve, reject) => {
   
   
	// 首先,先判断下promise是不是result, 因为我们知道,我们的result是一个返回值,他可能是一个Promise,那如果他直接返回第一个参数中的promise的话,那么是会造成死循环的。
	if (promise === result) {
   
   
		return new Error(''未知的result'')
	}
	// 判断是否是对象
	if (typeof result === ''object'' && result != null) {
   
   
		try {
   
   
			// 如果是对象,先看是否存在then函数
			let then = result.then
			// 如果result.then是一个函数,就说明是Promise对象,或者thenable对象
			if (typeof then === ''function'') {
   
   
				// 利用.call将this指向result, 防止result.then()报错
				then.call(result, res => {
   
   
					resolve(res)
				}, err => {
   
   
					reject(err)
				})
			} else {
   
   
				// 如果不是function,那么说明只是普通对象,并不是Promise对象,当普通值处理
				resolve(result)
			}
		} catch (err) {
   
   
			reject(err)
		}
	} else {
   
   
		// 不是对象,那就是普通值或者函数,直接resolve()
		resolve(result)
	}
}

到这一步,我们已经可以处理 return 一个 Promise 对象时的情况了。我们来验证一下

const promise1 = new MyPromise((resolve, reject) => {
   
   
    setTimeout(() => {
   
   
        resolve(123)
    }, 0)
})
promise1.then((res) => {
   
   
    console.log(res)
    return new MyPromise((resolve, reject) => {
   
   
        resolve(234)
    })
}, (err) => {
   
   
    console.log(err)
}).then(res => {
   
   
    console.log(res)
})
// 输出
123
234

看上图,返回一个 Promise 时,也可以正常处理。
但此时,又引出一个问题了,上图中,return 的 Promise 里面 ,如果我 resolve 的不是 234,而是 resolve 了一个新的 Promise 呢
看下面代码

const promise1 = new MyPromise((resolve, reject) => {
   
   
    setTimeout(() => {
   
   
        resolve(123)
    }, 0)
})
promise1.then((res) => {
   
   
    console.log(res)
    return new MyPromise((resolve, reject) => {
   
   
        const promise3 = new MyPromise((resolve, reject) => {
   
   
            resolve(234)
        })
        resolve(promise3)
    })
}, (err) => {
   
   
    console.log(err)
}).then(res => {
   
   
    console.log(res)
})
// 输出
123
MyPromise {
   
   
  status: ''fulfilled'',
  resolveValue: 234,
  rejectValue: null,
  onFullFilledList: [],
  onRejectedList: []
}

可以看出,这个时候,就解析不出 234 了。更别说如果 promise3 内部 resolve 的又是一个 Promise 了。此时,如果出现这个层层嵌套的。我们是不是要进一步的去进行解析啊,直到解析出一个普通值。那么这个时候,我们是不是要用到递归啊,不断的利用 fomatPromise 去解析 resolve 的值,直到 resolve 的是一个普通值才停止。下面我们看代码,继续完善 formatPromise

const formatPromise = (promise, result, resolve, reject) => {
   
   
	// 首先,先判断下promise是不是result, 因为我们知道,我们的result是一个返回值,他可能是一个Promise,那如果他直接返回第一个参数中的promise的话,那么是会造成死循环的。
	if (promise === result) {
   
   
		return new Error(''未知的result'')
	}
	// 判断是否是对象
	if (typeof result === ''object'' && result != null) {
   
   
		try {
   
   
			// 如果是对象,先看是否存在then函数
			let then = result.then
			// 如果result.then是一个函数,就说明是Promise对象,或者thenable对象
			if (typeof then === ''function'') {
   
   
				// 利用.call将this指向result, 防止result.then()报错
				then.call(result, res => {
   
   
					// 替换resolve(res)
					formatPromise(promise, res, resolve, reject)
				}, err => {
   
   
					reject(err)
				})
			} else {
   
   
				// 如果不是function,那么说明只是普通对象,并不是Promise对象,当普通值处理
				resolve(result)
			}
		} catch (err) {
   
   
			reject(err)
		}
	} else {
   
   
		// 不是对象,那就是普通值或者函数,直接resolve()
		resolve(result)
	}
}

此时,我们再来验证一下

const promise1 = new MyPromise((resolve, reject) => {
   
   
    setTimeout(() => {
   
   
        resolve(123)
    }, 0)
})
promise1.then((res) => {
   
   
    console.log(res)
    return new MyPromise((resolve, reject) => {
   
   
        const promise3 = new MyPromise((resolve, reject) => {
   
   
            resolve(234)
        })
        resolve(promise3)
    })
}, (err) => {
   
   
    console.log(err)
}).then(res => {
   
   
    console.log(res)
})
// 输出
123
234

到此,我们 then 相关的核心代码就已经完成了。下面我们来看下.catch ()

Promise.catch()

上一篇用法中,我们说过,其实.catch (),和.then () 的 reject 回调是一样的。只是使用位置不一样罢了。并且.catch () 也支持链式调用。也就是说.catch 和.then 其实是一样的,也返回了一个 Promise 对象对不对,因为这样才能链式调用。所以其实 catch 很简单,他其实内部就是执行了一个只有 reject 回调的 then 函数。下面我们看下代码

MyPromise.prototype.catch = (err) => {
   
   
    return this.then(undefined, err)
}

下面,我们来验证一下

const promise1 = new MyPromise((resolve, reject) => {
   
   
    setTimeout(() => {
   
   
        resolve(123)
    }, 0)
})
promise1.then((res) => {
   
   
    console.log(res)
    return new MyPromise((resolve, reject) => {
   
   
        reject(new Error(''345''))
    })
}, (err) => {
   
   
    console.log(1)
    console.log(err)
}).catch((err) => {
   
   
    console.log(2)
    console.log(err)
})
输出
123
2
Error: 345

现在,我们这个 Promise 核心代码好像是已经完成了是吧! 但其实,这里还有 bug。下面我们继续看下下面一段代码

const promise1 = new MyPromise((resolve, reject) => {
   
   
    setTimeout(() => {
   
   
        resolve(123)
    }, 0)
})
promise1.then((res) => {
   
   
    console.log(res)
    return new MyPromise((resolve, reject) => {
   
   
        reject(new Error(''345''))
    })
}, (err) => {
   
   
    console.log(1)
    console.log(err)
}).then((res) => {
   
   
    console.log(''第二个then'')
}).catch((err) => {
   
   
    console.log(2)
    console.log(err)
})
// 输出
123
2
TypeError: onRejected is not a function

哎,我们发现,当我们在触发错误的地方和 catch 函数之间插入了一个 then 的时候,发生了什么啊,我们发现,catch 的回调是执行了,但是这个错误并没有被抛出来,then 函数内部报错了。为什么啊。
这个时候我们就要想到一点,Promise 的错误捕获是不是一层层捕获的啊,按理说第一个 then 内部抛出了错误,我们是不是优先在第一个 then 后面的函数内进行捕获啊(也就是第二个 then 内),但是,由于我们并没有给第二个 then 定义一个错误捕获的函数,所以这个时候是不是就报错了啊 , 说 onRejected(也就是 then 的第二个参数)不是一个函数。但是原生 Promise 功能是怎样的啊。当没有第二个参数的时候,错误是不是会继续往下传递啊。所以这个时候,我们需要判断一下第二个参数到底有没有。如果没有,或者不是函数,我们是不是要将错误,继续往下抛出啊。下面我们改造下 then, 继续看代码

const isFun = (fun) => typeof fun === ''function''
MyPromise.prototype.then = function (onFullFilled, onRejected) {
   
   
    // 判断onRejected是否存在或者是否是函数,如果不是函数或者不存在,我们让它等于一个函数,并且在函数内继续将err向下抛出
    onRejected = isFun(onRejected) ? onRejected : err => {
   
   
        throw err
    }

	// 将then函数内部返回的Promise对象取名为promise2,后续文档中将直接以promise2来表示这个对象
    const promise2 = new MyPromise((resolve, reject) => {
   
   
        // onFulfilled, onRejected分别resolve()时的回调函数和reject()时的回调函数
        // 此时,判断状态,不同状态时,分别执行不同的回调
        if (this.status === ''fulfilled'') {
   
   
        	setTimeout(() => {
   
   
				try {
   
   
					// 定义一个变量来保存onFullFilled的返回值
		            let result = onFullFilled(this.resolveValue)
		            formatPromise(promise2, result, resolve, reject)
				} catch (err) {
   
   
					reject(err) // 捕捉上面代码执行的错误
				}
			}, 0) // 这里说明下为说明要用setTimeout, 因为我这段代码要用到promise2,而如果是同步代码,promise2不可在自己的立即执行函数内调用自己
        }
        if (this.status === ''rejected'') {
   
   
        	setTimeout(() => {
   
   
        		try {
   
   
					// 定义一个变量来保存onRejected的返回值
            		let result = onRejected(this.rejectValue)
		            formatPromise(promise2, result, resolve, reject)
				} catch (err) {
   
   
					reject(err) // 捕捉上面代码执行的错误
				}
			}, 0)
        }
        if (this.status === ''pending'') {
   
   
            this.onFullFilledList.push(() => {
   
   
                setTimeout(() => {
   
   
					try {
   
   
						// 定义一个变量来保存onFullFilled的返回值
			            let result = onFullFilled(this.resolveValue)
			            formatPromise(promise2, result, resolve, reject)
					} catch (err) {
   
   
						reject(err) // 捕捉上面代码执行的错误
					}
				}, 0)
            })
            this.onRejectedList.push(() => {
   
   
                setTimeout(() => {
   
   
	        		try {
   
   
						// 定义一个变量来保存onRejected的返回值
	            		let result = onRejected(this.rejectValue)
			            formatPromise(promise2, result, resolve, reject)
					} catch (err) {
   
   
						reject(err) // 捕捉上面代码执行的错误
					}
				}, 0)
            })
        }
    })
    return promise2
}

此时,我们再验证下

const promise1 = new MyPromise((resolve, reject) => {
   
   
    setTimeout(() => {
   
   
        resolve(123)
    }, 0)
})
promise1.then((res) => {
   
   
    console.log(res)
    return new MyPromise((resolve, reject) => {
   
   
        reject(new Error(''345''))
    })
}, (err) => {
   
   
    console.log(1)
    console.log(err)
}).then((res) => {
   
   
    console.log(''第二个then'')
}).catch((err) => {
   
   
    console.log(2)
    console.log(err)
})
// 输出
123
2
Error: 345

好,catch 的错误一步一步向下传递的问题我们解决了。那么 then 是不是也有这样的问题啊,then 函数的值一步一步向下传递的问题我们是不是还没解决?大家还记得我们上篇文章中提到的值穿透的现象吗?当我们的 then 函数的第一个参数不存在,或者不是函数时,他的值是不是会穿透到第二个 then 的 resolve 回调中啊。怎么实现呢,原理和 catch 其实是一样的。话不多说,直接看代码

const isFun = (fun) => typeof fun === ''function''
MyPromise.prototype.then = function (onFullFilled, onRejected) {
   
   
    // 判断onRejected是否存在或者是否是函数,如果不是函数或者不存在,我们让它等于一个函数,并且在函数内继续将err向下抛出
    onRejected = isFun(onRejected) ? onRejected : err => {
   
   
        throw err
    }
    onFullFilled = isFun(onFullFilled)? onFullFilled : res => res

	// 将then函数内部返回的Promise对象取名为promise2,后续文档中将直接以promise2来表示这个对象
    const promise2 = new MyPromise((resolve, reject) => {
   
   
        // onFulfilled, onRejected分别resolve()时的回调函数和reject()时的回调函数
        // 此时,判断状态,不同状态时,分别执行不同的回调
        if (this.status === ''fulfilled'') {
   
   
        	setTimeout(() => {
   
   
				try {
   
   
					// 定义一个变量来保存onFullFilled的返回值
		            let result = onFullFilled(this.resolveValue)
		            formatPromise(promise2, result, resolve, reject)
				} catch (err) {
   
   
					reject(err) // 捕捉上面代码执行的错误
				}
			}, 0) // 这里说明下为说明要用setTimeout, 因为我这段代码要用到promise2,而如果是同步代码,promise2不可在自己的立即执行函数内调用自己
        }
        if (this.status === ''rejected'') {
   
   
        	setTimeout(() => {
   
   
        		try {
   
   
					// 定义一个变量来保存onRejected的返回值
            		let result = onRejected(this.rejectValue)
		            formatPromise(promise2, result, resolve, reject)
				} catch (err) {
   
   
					reject(err) // 捕捉上面代码执行的错误
				}
			}, 0)
        }
        if (this.status === ''pending'') {
   
   
            this.onFullFilledList.push(() => {
   
   
                setTimeout(() => {
   
   
					try {
   
   
						// 定义一个变量来保存onFullFilled的返回值
			            let result = onFullFilled(this.resolveValue)
			            formatPromise(promise2, result, resolve, reject)
					} catch (err) {
   
   
						reject(err) // 捕捉上面代码执行的错误
					}
				}, 0)
            })
            this.onRejectedList.push(() => {
   
   
                setTimeout(() => {
   
   
	        		try {
   
   
						// 定义一个变量来保存onRejected的返回值
	            		let result = onRejected(this.rejectValue)
			            formatPromise(promise2, result, resolve, reject)
					} catch (err) {
   
   
						reject(err) // 捕捉上面代码执行的错误
					}
				}, 0)
            })
        }
    })
    return promise2
}

我们再来验证下值穿透的现象

const promise1 = new MyPromise((resolve, reject) => {
   
   
    setTimeout(() => {
   
   
        resolve(123)
    }, 0)
})
promise1.then(''aaa'', (err) => {
   
   
    console.log(1)
    console.log(err)
}).then((res) => {
   
   
    console.log(''第二个then'')
    console.log(res)
}).catch((err) => {
   
   
    console.log(2)
    console.log(err)
})
// 输出
第二个then
123

好了,这个时候我们的代码功能就已经完整了。下面附上全部代码

const isFun = (fun) => typeof fun === ''function''
const formatPromise = (promise, result, resolve, reject) => {
   
   
	// 首先,先判断下promise是不是result, 因为我们知道,我们的result是一个返回值,他可能是一个Promise,那如果他直接返回第一个参数中的promise的话,那么是会造成死循环的。
	if (promise === result) {
   
   
		return new Error(''未知的result'')
	}
	// 判断是否是对象
	if (typeof result === ''object'' && result != null) {
   
   
		try {
   
   
			// 如果是对象,先看是否存在then函数
			let then = result.then
			// 如果result.then是一个函数,就说明是Promise对象,或者thenable对象
			if (typeof then === ''function'') {
   
   
				// 利用.call将this指向result, 防止result.then()报错
				then.call(result, res => {
   
   
					formatPromise(promise, res, resolve, reject)
				}, err => {
   
   
					reject(err)
				})
			} else {
   
   
				// 如果不是function,那么说明只是普通对象,并不是Promise对象,当普通值处理
				resolve(result)
			}
		} catch (err) {
   
   
			reject(err)
		}
	} else {
   
   
		// 不是对象,那就是普通值或者函数,直接resolve()
		resolve(result)
	}
}

class MyPromise {
   
   
    constructor (fun) {
   
   
        // 定义初始状态(3个状态分别是pending, fulfilled, rejected)
        this.status = ''pending''
        // 定义两个变量分别来存储成功时值和失败时的值
        this.resolveValue = null
        this.rejectValue = null
        this.onFullFilledList = []
        this.onRejectedList = []

        // 定义resolve函数
        let resolve = (val) => {
   
   
            // 1、将状态变更为fulfilled, 但是注意一点,Promise是有个特点的,就是状态只能由pending状态变更为fulfilled或者由pending状态变更为rejected。且,状态变化后,不会再变化。故,我们需要先判断当前是否是等待状态pending
            if (this.status === ''pending'') {
   
     // this指向实例化出来Promise对象
                this.status = ''fulfilled''
                // 2、保存resolve时的值,以便后面调用then()方法时使用
                this.resolveValue = val

                // 执行then的resolve回调
                this.onFullFilledList.forEach(funItem => funItem())
            }
        }
        
        // 定义reject函数
        let reject = (val) => {
   
   
            // 1、状态变更为rejected
            if (this.status === ''pending'') {
   
   
                this.status = ''rejected''
                // 2、保存reject()时的值
                this.rejectValue = val

                // 执行then的reject回调
                this.onRejectedList.forEach(funItem => funItem())
            }
        }

        try {
   
   
            fun(resolve, reject)
        } catch (err) {
   
   
            reject(err)
        }
    }

}

MyPromise.prototype.then = function (onFullFilled, onRejected) {
   
   
    // 判断onRejected是否存在或者是否是函数,如果不是函数或者不存在,我们让它等于一个函数,并且在函数内继续将err向下抛出
    onRejected = isFun(onRejected) ? onRejected : err => {
   
   
        throw err
    }
    onFullFilled = isFun(onFullFilled) ? onFullFilled : res => res

	// 将then函数内部返回的Promise对象取名为promise2,后续文档中将直接以promise2来表示这个对象
    const promise2 = new MyPromise((resolve, reject) => {
   
   
        // onFulfilled, onRejected分别resolve()时的回调函数和reject()时的回调函数
        // 此时,判断状态,不同状态时,分别执行不同的回调
        if (this.status === ''fulfilled'') {
   
   
        	setTimeout(() => {
   
   
				try {
   
   
					// 定义一个变量来保存onFullFilled的返回值
		            let result = onFullFilled(this.resolveValue)
		            formatPromise(promise2, result, resolve, reject)
				} catch (err) {
   
   
					reject(err) // 捕捉上面代码执行的错误
				}
			}, 0) // 这里说明下为说明要用setTimeout, 因为我这段代码要用到promise2,而如果是同步代码,promise2不可在自己的立即执行函数内调用自己
        }
        if (this.status === ''rejected'') {
   
   
        	setTimeout(() => {
   
   
        		try {
   
   
					// 定义一个变量来保存onRejected的返回值
            		let result = onRejected(this.rejectValue)
		            formatPromise(promise2, result, resolve, reject)
				} catch (err) {
   
   
					reject(err) // 捕捉上面代码执行的错误
				}
			}, 0)
        }
        if (this.status === ''pending'') {
   
   
            this.onFullFilledList.push(() => {
   
   
                setTimeout(() => {
   
   
					try {
   
   
						// 定义一个变量来保存onFullFilled的返回值
			            let result = onFullFilled(this.resolveValue)
			            formatPromise(promise2, result, resolve, reject)
					} catch (err) {
   
   
						reject(err) // 捕捉上面代码执行的错误
					}
				}, 0)
            })
            this.onRejectedList.push(() => {
   
   
                setTimeout(() => {
   
   
	        		try {
   
   
						// 定义一个变量来保存onRejected的返回值
	            		let result = onRejected(this.rejectValue)
			            formatPromise(promise2, result, resolve, reject)
					} catch (err) {
   
   
						reject(err) // 捕捉上面代码执行的错误
					}
				}, 0)
            })
        }
    })
    return promise2
}

MyPromise.prototype.catch = function (err) {
   
   
    return this.then(undefined, err)
}

const promise1 = new MyPromise((resolve, reject) => {
   
   
    setTimeout(() => {
   
   
        resolve(123)
    }, 0)
})
promise1.then(''aaa'', (err) => {
   
   
    console.log(1)
    console.log(err)
}).then((res) => {
   
   
    console.log(''第二个then'')
    console.log(res)
}).catch((err) => {
   
   
    console.log(2)
    console.log(err)
})

核心代码我们已经实现了,剩下的几个 Promise.Resolve (),Promise.Reject (),Promise.all (),Promise.race () 就比较简单了。我们就不做过多的说明了。大家直接看代码吧

Promise.resolve()

const isPromise = (value) => {
   
   
    if ((value != null && typeof value === ''object'') || typeof value === ''function'') {
   
   
        if (typeof value.then == ''function'') {
   
   
            return true
        }
    } else {
   
   
        return false
    }
}
MyPromise.resolve = (value) => {
   
   
    // 如果是一个promise对象就直接将这个对象返回
    if (isPromise(value)) {
   
   
        return value
    } else {
   
   
        // 如果是一个普通值就将这个值包装成一个promise对象之后返回
        return new MyPromise((resolve, reject) => {
   
   
            resolve(value)
        })
    }
}

Promise.reject()

MyPromise.reject = (value) => {
   
   
    return new MyPromise((resolve, reject) => {
   
   
        reject(value)
    })
}

Promise.all()

all 的特点就是如果有其中一个返回了错误(reject),那么就立即返回错误。否则,必须等到所有的都成功之后才会返回

MyPromise.all = (arr) => {
   
   
    // 返回一个promise
    return new MyPromise((resolve, reject) => {
   
   
        let resArr = [] // 存储处理的结果的数组
        // 判断每一项是否处理完了
        let index = 0
        function processData(i, data) {
   
   
            resArr[i] = data
            index += 1
            if (index == arr.length) {
   
   
                // 处理异步,要使用计数器,不能使用resArr==arr.length
                resolve(resArr)
            }
        }
        for (let i = 0; i < arr.length; i++) {
   
   
            if (isPromise(arr[i])) {
   
   
                arr[i].then((data) => {
   
   
                    processData(i, data)
                }, (err) => {
   
   
                    reject(err) // 只要有一个传入的promise没执行成功就走reject
                    return
                })
            } else {
   
   
                processData(i, arr[i])
            }
        }
    })
}

Promise.race()

race 的特点是,哪个先返回状态,就立即返回这个的状态和值(和赛跑一样,哪个先到,我就用哪个)

MyPromise.race = (arr) => {
   
   
    return new MyPromise((resolve, reject) => {
   
   
        for (let i = 0; i < arr.length; i++) {
   
   
            if (isPromise(arr[i])) {
   
   
                arr[i].then((data) => {
   
   
                    resolve(data)// 哪个先完成就返回哪一个的结果
                    return
                }, (err) => {
   
   
                    reject(err)
                    return
                })
            } else {
   
   
                resolve(arr[i])
            }
        }
    })
}

好了,代码都整完了。有不懂的,可以私信我或者直接评论中问。最后再附上一分最全的代码,感谢阅览!

// 是否是函数
const isFun = (fun) => typeof fun === ''function''

// 是否是Promise对象
const isPromise = (value) => {
   
   
    if ((value != null && typeof value === ''object'') || typeof value === ''function'') {
   
   
        if (typeof value.then == ''function'') {
   
   
            return true
        }
    } else {
   
   
        return false
    }
}
// then函数中,返回值的处理函数,判断返回值的类型,并做处理
const formatPromise = (promise, result, resolve, reject) => {
   
   
	// 首先,先判断下promise是不是result, 因为我们知道,我们的result是一个返回值,他可能是一个Promise,那如果他直接返回第一个参数中的promise的话,那么是会造成死循环的。
	if (promise === result) {
   
   
		return new Error(''未知的result'')
	}
	// 判断是否是对象
	if (typeof result === ''object'' && result != null) {
   
   
		try {
   
   
			// 如果是对象,先看是否存在then函数
			let then = result.then
			// 如果result.then是一个函数,就说明是Promise对象,或者thenable对象
			if (typeof then === ''function'') {
   
   
				// 利用.call将this指向result, 防止result.then()报错
				then.call(result, res => {
   
   
					formatPromise(promise, res, resolve, reject)
				}, err => {
   
   
					reject(err)
				})
			} else {
   
   
				// 如果不是function,那么说明只是普通对象,并不是Promise对象,当普通值处理
				resolve(result)
			}
		} catch (err) {
   
   
			reject(err)
		}
	} else {
   
   
		// 不是对象,那就是普通值或者函数,直接resolve()
		resolve(result)
	}
}

class MyPromise {
   
   
    constructor (fun) {
   
   
        // 定义初始状态(3个状态分别是pending, fulfilled, rejected)
        this.status = ''pending''
        // 定义两个变量分别来存储成功时值和失败时的值
        this.resolveValue = null
        this.rejectValue = null
        this.onFullFilledList = []
        this.onRejectedList = []

        // 定义resolve函数
        let resolve = (val) => {
   
   
            // 1、将状态变更为fulfilled, 但是注意一点,Promise是有个特点的,就是状态只能由pending状态变更为fulfilled或者由pending状态变更为rejected。且,状态变化后,不会再变化。故,我们需要先判断当前是否是等待状态pending
            if (this.status === ''pending'') {
   
     // this指向实例化出来Promise对象
                this.status = ''fulfilled''
                // 2、保存resolve时的值,以便后面调用then()方法时使用
                this.resolveValue = val

                // 执行then的resolve回调
                this.onFullFilledList.forEach(funItem => funItem())
            }
        }
        
        // 定义reject函数
        let reject = (val) => {
   
   
            // 1、状态变更为rejected
            if (this.status === ''pending'') {
   
   
                this.status = ''rejected''
                // 2、保存reject()时的值
                this.rejectValue = val

                // 执行then的reject回调
                this.onRejectedList.forEach(funItem => funItem())
            }
        }

        try {
   
   
            fun(resolve, reject)
        } catch (err) {
   
   
            reject(err)
        }
    }

}

MyPromise.prototype.then = function (onFullFilled, onRejected) {
   
   
    // 判断onRejected是否存在或者是否是函数,如果不是函数或者不存在,我们让它等于一个函数,并且在函数内继续将err向下抛出
    onRejected = isFun(onRejected) ? onRejected : err => {
   
   
        throw err
    }
    onFullFilled = isFun(onFullFilled) ? onFullFilled : res => res

	// 将then函数内部返回的Promise对象取名为promise2,后续文档中将直接以promise2来表示这个对象
    const promise2 = new MyPromise((resolve, reject) => {
   
   
        // onFulfilled, onRejected分别resolve()时的回调函数和reject()时的回调函数
        // 此时,判断状态,不同状态时,分别执行不同的回调
        if (this.status === ''fulfilled'') {
   
   
        	setTimeout(() => {
   
   
				try {
   
   
					// 定义一个变量来保存onFullFilled的返回值
		            let result = onFullFilled(this.resolveValue)
		            formatPromise(promise2, result, resolve, reject)
				} catch (err) {
   
   
					reject(err) // 捕捉上面代码执行的错误
				}
			}, 0) // 这里说明下为说明要用setTimeout, 因为我这段代码要用到promise2,而如果是同步代码,promise2不可在自己的立即执行函数内调用自己
        }
        if (this.status === ''rejected'') {
   
   
        	setTimeout(() => {
   
   
        		try {
   
   
					// 定义一个变量来保存onRejected的返回值
            		let result = onRejected(this.rejectValue)
		            formatPromise(promise2, result, resolve, reject)
				} catch (err) {
   
   
					reject(err) // 捕捉上面代码执行的错误
				}
			}, 0)
        }
        if (this.status === ''pending'') {
   
   
            this.onFullFilledList.push(() => {
   
   
                setTimeout(() => {
   
   
					try {
   
   
						// 定义一个变量来保存onFullFilled的返回值
			            let result = onFullFilled(this.resolveValue)
			            formatPromise(promise2, result, resolve, reject)
					} catch (err) {
   
   
						reject(err) // 捕捉上面代码执行的错误
					}
				}, 0)
            })
            this.onRejectedList.push(() => {
   
   
                setTimeout(() => {
   
   
	        		try {
   
   
						// 定义一个变量来保存onRejected的返回值
	            		let result = onRejected(this.rejectValue)
			            formatPromise(promise2, result, resolve, reject)
					} catch (err) {
   
   
						reject(err) // 捕捉上面代码执行的错误
					}
				}, 0)
            })
        }
    })
    return promise2
}

MyPromise.prototype.catch = function (err) {
   
   
    return this.then(undefined, err)
}

MyPromise.resolve = (value) => {
   
   
    // 如果是一个promise对象就直接将这个对象返回
    if (isPromise(value)) {
   
   
        return value
    } else {
   
   
        // 如果是一个普通值就将这个值包装成一个promise对象之后返回
        return new MyPromise((resolve, reject) => {
   
   
            resolve(value)
        })
    }
}

MyPromise.reject = (value) => {
   
   
    return new MyPromise((resolve, reject) => {
   
   
        reject(value)
    })
}

MyPromise.all = (arr) => {
   
   
    // 返回一个promise
    return new MyPromise((resolve, reject) => {
   
   
        let resArr = [] // 存储处理的结果的数组
        // 判断每一项是否处理完了
        let index = 0
        function processData(i, data) {
   
   
            resArr[i] = data
            index += 1
            if (index == arr.length) {
   
   
                // 处理异步,要使用计数器,不能使用resArr==arr.length
                resolve(resArr)
            }
        }
        for (let i = 0; i < arr.length; i++) {
   
   
            if (isPromise(arr[i])) {
   
   
                arr[i].then((data) => {
   
   
                    processData(i, data)
                }, (err) => {
   
   
                    reject(err) // 只要有一个传入的promise没执行成功就走reject
                    return
                })
            } else {
   
   
                processData(i, arr[i])
            }
        }
    })
}

MyPromise.race = (arr) => {
   
   
    return new MyPromise((resolve, reject) => {
   
   
        for (let i = 0; i < arr.length; i++) {
   
   
            if (isPromise(arr[i])) {
   
   
                arr[i].then((data) => {
   
   
                    resolve(data)// 哪个先完成就返回哪一个的结果
                    return
                }, (err) => {
   
   
                    reject(err)
                    return
                })
            } else {
   
   
                resolve(arr[i])
            }
        }
    })
}

const promise1 = new MyPromise((resolve, reject) => {
   
   
    setTimeout(() => {
   
   
        resolve(123)
    }, 0)
})
promise1.then(''aaa'', (err) => {
   
   
    console.log(1)
    console.log(err)
}).then((res) => {
   
   
    console.log(''第二个then'')
    console.log(res)
}).catch((err) => {
   
   
    console.log(2)
    console.log(err)
})

关于测试时出现“不建议使用mpromiseMongoose的默认promise库”错误的问题我们已经讲解完毕,感谢您的阅读,如果还想了解更多关于11-利用Promise的图片异步加载 / Promise封装ajax,模拟axios / Promise的finally原理、angularjs – chai-as-promise测试不适用于$q promises、ES6 Promise --回调与Promise的对比、信任问题、错误处理、Promise的状态、以及Promise对象的常用方法、ES6 Promise 源码解析 (从 Promise 功能的角度看 Promise 源码实现)等相关内容,可以在本站寻找。

本文标签: