ES6 Promise 对象

1、Promise 的含义

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

Promise对象特点

  1. 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
  2. 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。

Promise缺点

  1. 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
  2. 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
  3. 当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

Promise对象主要功能是解决异步的回调问题

2、同步和异步

cpu面对一个长时间、其他设备处理,比如磁盘I/O,数据库查询、写入等等这些操作的时候,此时有两种模式:第一种是同步模式,死等这个设备处理完成,此时cpu被堵塞了;第二种就是异步模式,不等这个设备处理完成,而是先执行后面的语句,等这个设备处理完成之后执行回调函数。

异步的小案例

先让cpu处理累加计算的逻辑,然后再命令硬盘异步读取文件,此时读取过程中没有堵塞进程,cpu提前执行后面的累加程序,等读取完毕之后再执行读取内部的回调函数

var fs = require("fs");
// 0-100的累加
for(var i = 0,sum = 0; i <= 100; i++) {
  sum += i;
}
console.log(sum);

// 读取文件
fs.readFile("./txt/01.txt",(err,content)=>{
  console.log(content.toString())
})

// 0-100的累加
for(var i = 0,sum = 0; i <= 100; i++) {
  sum += i;
}
console.log(sum);

同步小案例

var fs = require("fs");

// 0-100的累加
for(var i = 0,sum = 0; i <= 100; i++) {
  sum += i;
}
console.log(sum);

// 同步读取文件
var content = fs.readFileSync("./txt/01.txt")
console.log(content.toString())
// 0-100的累加
for(var i = 0,sum = 0; i <= 100; i++) {
  sum += i;
}
console.log(sum);

同步和异步的区别从语法上看,同步是在等号左侧接收,异步是在回调函数内部接收结果

除了处理语法层面上不同之外,时间上也有区别,同步代码的时间,会比异步代码的耗费时间要长

3、异步的语法缺点

3.1 代码不美观

fs.readFile("./txt/01.txt",(err,content)=>{
  console.log(content.toString())
  fs.readFile("./txt/02.txt",(err,content)=>{
    console.log(content.toString());
    fs.readFile("./txt/03.txt",(err,content)=>{
      console.log(content.toString());
    })
  })
})

同步的话看起来美观多了

console.log(fs.readFileSync("./txt/01.txt"));
console.log(fs.readFileSync("./txt/02.txt"));
console.log(fs.readFileSync("./txt/03.txt"));

3.2 代码的维护性和机构性不强

promise来解决这个问题

4、Promise的基本使用

var fs = require("fs")

function rFile(url){
  return new Promise((resolve,reject)=>{
    fs.readFile(url,(err,content)=>{
      if(err) {
        reject(err)
        return;
      }
      resolve(content.toString())
    })
  })
}

rFile("./txt/01.txt").then((data)=>{
  console.log(data);
  return rFile("./txt/02.txt")
})
.then(data=>{
  console.log(data);
  return rFile("./txt/03.txt")
})
.then(data=>{
  console.log(data);
})
  • rFile函数返回了一个Promise的对象,这个对象就是构造函数,是ES6新增的
  • Promise一定是某一个函数的返回值,Promise必须当做一个函数的返回值才有意义,上面代码中Promise就是rFile函数的返回值
  • Promise在被new的时候要求传入一个函数,这个函数就是Promise的回调函数,这个函数有两个形参,分别是resolve和reject;resolve表示成功之后做的事情,reject表示失败之后做的事情

then和catch

下面代码中then的部分表示的就是读取成功之后的操作,catch表示的是读取失败的操作

rFile("./txt/011.txt")
.then((data)=>{
  console.log(data)
}).catch(err=>{
  console.log("读取错误",err);
})

下面的catch一旦遇到了错误就会阻止整个程序的执行,比如读取两个文件,第一个文件读取失败了

rFile("./txt/011.txt").then((data)=>{
  console.log(data);
  return rFile("./txt/02.txt")
}).then((data)=>{
  console.log(123)
}).catch(err=>{
  console.log("读取错误",err);
})

上面代码中,由于没有011.txt,所以会走catch,此时你会发现第二个then中123也没有输出

如果想要输出第二个then中的data可以使用单个then报错的方式,第二个参数

rFile("./txt/011.txt").then((data)=>{
  console.log(data);
  return rFile("./txt/02.txt")
},(err)=>{
  console.log("读取错误1",err);
}).then((data)=>{
  console.log(123)
})

上的代码中虽然011.txt没有抛出错误了,但是没有阻止下一个then的执行,输出了123

总结:

  • Promise函数中,内部的语句一定是异步语句,异步语句的成功将通过resolve(成功的数据)传出去,这个数据将成为后面调用的then函数中的data返回值,异步语句的失败将通过reject(失败的数据)传出去,这个数据将成为后面调用的catch函数中的err返回值,失败的语句也可以是then的第二个参数
  • Promise的实例拥有then的能力,then里面接收一个参数,data就是创建promise的时候的resolve;then进行连续打点

then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

rFile("./txt/01.txt").then((data)=>{
  console.log(data);
  return rFile("./txt/02.txt")
}).then((data)=>{
  console.log(data)
  return rFile("./txt/03.txt")
}).then(data=>{
  console.log(data)
})

5、非Node环境使用Promise

5.1浏览器直接调用 ajax读取文件

    <script>
        $("h1").click(function () {
            $.get("txt/01.txt", function (data) {
                console.log(data);
                $.get("txt/02.txt", function (data) {
                    console.log(data);
                    $.get("txt/03.txt", function (data) {
                        console.log(data)
                    })
                })
            })
        })
    </script>

缺点:回调黑洞问题 

5.2 浏览器使用 Promise 解决回调黑洞

function readFile(url) {
    return new Promise((resolve, reject) => {
        $.get(url, (data) => {
            resolve(data);
        })
    })
}
$("h1").click(function () {
    readFile("./txt/01.txt").then((data) => {
        console.log(data);
        return readFile("./txt/02.txt")
    }).then(data => {
        console.log(data);
        return readFile("./txt/03.txt")
    }).then(data => {
        console.log(data)
    })
})

Promise本质上就是一个“语法糖”

语法糖?

语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会

所以Promise其实就是改变了异步的语法,本质没有改变异步,而是将异步嵌套改为then的连续打点写法

6、Promise.resolve方法

Promise可以.then调用是因为当前的实例是一个promise对象,promise对象是怎么形成的?

可以用promise.resolve方法去创建一个对象

var pr = Promise.resolve("hello");
console.log(pr) 

node 07.js 

上面的代码将"hello"封装成了一个带有promise对象的壳子

此时这个pr就可以.then去使用

var pr = Promise.resolve("hello");
pr.then(data=>{
    console.log(data)
})

如果resolve方法的参数是一个对象内部有then方法

Promise.resolve方法会将这个对象转为Promise对象,然后就立即执行thenable对象的then方法

let thenable ={
    then : function (resolve,reject) {
        resolve('hello')
    }
}
let p1 = Promise.resolve(thenable)
p1.then(function (value) {
    console.log(value)
})

7、Promise.all方法

Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例

var fs = require("fs");

function readFile(url) {
    return new Promise((resolve,reject) => {
        fs.readFile(url,(err,content) =>{
            if(err){
                reject(err);
                return;
            }
            resolve(content.toString())
        })
    })
}
var p1 = readFile("project/txt/01.txt")
var p2 = readFile("project/txt/02.txt")
var p3 = readFile("project/txt/03.txt")
Promise.all([p1,p2,p3]).then(data=>{
    console.log(data)
}).catch(err => {
    console.log(err);
})

上面代码中,Promise.all方法接受一个数组作为参数,p1、p2、p3都是Promise对象的实例,如果不是,就会先调用到Promise.resolve方法,将参数转为Promise实例,再进一步处理

p的状态由p1、p2、p3决定,分成两种情况。

(1)只有p1、p2、p3的状态都变成成功状态,p的状态才会变成成功,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1、p2、p3之中有一个为失败,p的状态就变成失败,此时第一个被reject的实例的返回值,会传递给promise.all的catch回调函数

8、Promise.race方法

Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例

var fs = require("fs")

function readFile(url) {
    return new Promise((resolve,reject) =>{
        fs.readFile(url,(err,content) =>{
            if(err){
                reject(err);
                return;
            }
            resolve(content.toString())
        })
    })
}
var p1 = readFile("project/txt/01.txt")
var p2 = readFile("project/txt/02.txt")
var p3 = readFile("project/txt/03.txt")

var p = Promise.race([p1,p2,p3])
p.then(data =>{
    console.log(data)
}).catch(err=>{
    console.log(err)
})

和promise.all方法不同的是只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

9、Promise.done方法-非ES6提供

Promise对象的回调链,不管以then方法或catch方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为Promise内部的错误不会冒泡到全局)。因此,我们可以提供一个done方法,总是处于回调链的尾端,保证抛出任何可能出现的错误。

var fs = require("fs");
Promise.prototype.done = function(onFulfilled, onRejected){
  this.then(onFulfilled, onRejected)
    .catch(function (reason) {
      // 抛出一个全局错误
      setTimeout(() => { console.error("信息错误") }, 0);
    });
}
function readFile(url){
  return new Promise((resolve,reject)=>{
    fs.readFile(url,(err,content)=>{
      if(err){
        reject(err)
        return;
      }
      resolve(content.toString())
    })
  })
}

var p1 = readFile("txt/011.txt")

var p = Promise.race([p1])
p.then(data=>{
  console.log(data);
}).done()

上面的011.txt是没有的所以抛出全局错误

10、Promise.finally方法-非ES6提供

finally方法用于指定不管Promise对象最后状态如何,都会执行的操作。它与done方法的最大区别,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。

var fs = require("fs");
Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};
function readFile(url){
  return new Promise((resolve,reject)=>{
    fs.readFile(url,(err,content)=>{
      if(err){
        reject(err)
        return;
      }
      resolve(content.toString())
    })
  })
}

var p1 = readFile("txt/01.txt")

var p = Promise.race([p1])
p.then(data=>{
  console.log(data);
}).finally(()=>{
  console.log("最后的执行");
})

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值