Promise的基本使用

122 阅读47分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第9天,[点击查看活动详情](juejin.cn/post/708080… )。

promise初使用

promise是es6是新增的构造器,用来提供另一种异步代码的实现方案。本文介绍了Promise的基本使用,并给出了用它来改写异步回调函数的套路。

1 简介

本文介绍了Promise的构造器的使用语法,promise对象的三种状态及状态转换,并分析了其经典格式的执行流程,最后给出了用它来改写异步代码的套路,并予以实践。

你最好需要有一些ajax,箭头函数的基础这会理解更深一些。当然啦,也有可能你还没有需要用它们的时候。

2 主要内容

•什么是promise

•Promise构造器

•经典使用格式

•使用Promise改造回调函数

3 什么是Promise

promise:承诺。

生活中它是用来表述 对将来要发生的事情的肯定。例如 :高中生说,老师,我会考上一所好大学的;销售员说,老板,我一定会签下大单的;程序员说,老妈,我过年一定会带个女朋友回来的。

在ES6中,它是新增的构造器,用来优化实现异步操作。在没有它之前,javascript中的异步处理,大多是利用回调函数来实现的。

典型的几种如下:(1)setTimeout (2)ajax (3)nodejs中的文件读取

现在有了promise,就可以对这些异步操作进行改写了,当然了,改写嘛就是说之前的不好,有缺点所以要改。这里隐藏一个前提:先要知道它的坏处。不过,就算还没有能体会到坏处,那也可以先感受一下Promise的用法。例如在处理ajax请求时,在jquery.ajax方法和axios库中均提供了Promise格式的使用方式,例如:

基本的ajax方法如下:

$.ajax({    url: '',    data:{},    success(res) {}})

可以改写成

$.ajax({url, data}).then(res=>{})

即把success回调函数放在then中执行。

另外,如axios库,它本身就是基于promise的库,用法如下:

// 为给定 ID 的 user 创建请求
axios.get('/user?ID=12345').then(
    function (response) {
       console.log(response);
    }).catch(function (error) { 
         console.log(error);
    });

以上代码的来源axios-js[1]

那上面的.then.catch是写法是否给你带来困扰呢,学习完了本文的内容,你将会轻易写出如上的代码啦。

4 Promise构造器

在 ES6 中,用Promise来创建Promise类型的对象 。这一点就像使用new Array来创建数组。

4.1 Promise的格式

var p1 = new Promise(function(resolve,reject){  
        // 执行异步代码  
        // 调用resolve,或者reject
});

要点:

•构造器必须要给定一个参数,如果不给就是会报错。例如,new Promise() 报错的信息是: Promise resolver undefined is not a function•构造器的实参是一个函数,这个函数的特殊之处在于它有两个形参(resolve,reject),这两个形参也是函数。在格式上,也可以采用箭头函数来改写。例如:var p1 = new Promise((resolve,reject)=>{})。•在函数体的内部, 一般会执行异步代码,然后根据情况来调用resolve()或者是reject() 。调用resolve或者是reject后会产生什么样的后果,在后面小节介绍。当然了,再次强调一下resolve和reject只是形参名,可以改写成其它的。•构造器中的函数体中的代码会立即执行。这一点非常重要:

执行如下代码

console.log(1)var p1 = new Promise(function(resolve,reject){  // 执行异步代码,  // 调用resolve,或者reject  console.log(2)});console.log(3)

会输出1,2,3。

4.2 Promise 的三种状态和值

一个Promise对象的状态可能是如下三种之一:pending,resolved,rejected 。下面分别介绍。

4.2.1 pending

pending。它的意思是 "待定的,将发生的",相当于是一个初始状态。创建Promise对象时,且没有调用resolve或者是reject方法,相当于是初始状态。这个初始状态会随着你调用resolve,或者是reject函数而切换到另一种状态。

var p = new Promise((resolve,reject)=>{})console.dir(p)

这个初始状态就是你对别人说出承诺的那个瞬间,此时,大家都不知道这个承诺是否能兑现,可能是世间求得双全法,不负如来不负卿;也可能是心比天高,命比纸薄。未来的事,谁知道呢?

4.2.2 resolved

resolved。表示解决了,就是说这个承诺实现了。要实现从pending到resolved的转变,需要在 创建Promise对象时,在函数体中调用了resolve方法(即第一个参数)。

var p = new Promise((resolve,reject)=>{   resolved();})console.dir(p)

注意,上面的resolve和reject仅是形参名而已。

在chrome浏览器中的结果如下:

在firefox浏览器中的结果如下:

它们的值分别是resolved和fulfilled,稍有些区别。

4.2.3 rejected

rejected。拒绝,失败。表示这个承诺没有做到,失败了。要实现从pending到rejected的转换,只需要在创建Promise对象时,调用reject函数。

var p = new Promise((resolve,reject)=>{ reject();})console.dir(p)

4.2.4 三种状态小结

1.状态是可转化。

最初创建promise对象时,默认状态是pending,如果在函数体内部调用了第一个参数对应的函数,则状态变成了resolved;如果调用了第二个参数对应的函数,则状态变成了rejected。

 pending -----  resolve() --> resolved; pending -----  reject()  --> rejected;

2.状态转换是不可逆的。

一旦从pending ---> resolved(或者是rejected),就不可能再回到pending,也不能由resolved变成rejected。

4.3 promise的值

一个promise对象除了状态之外,还有promiseValue。在构造器中,这个值在调用resolve和reject方法时传入。

例如:

var p = new Promise((resolve,reject) => {   
    resolve(123); });//  此时,prommise对象p的状态是 resolved,值是123。



var p = new Promise( (resolve,reject) => {  
    reject(123); });//  此时,prommise对象p的状态是 rejected,值是123

这里可以传递任意数据类型,上面是数值。

单独来看promise的值和状态似乎没有什么意义,它的使用场景在于结合promise对象的实例方法一起来用来。下面来看经典使用。

5 经典使用格式

var p1 = new Promise(function(resolve,reject){    //异步操作     // resolve(obj1)    //或者 reject(obj2)});p1.then(function(rs){    // 如果p1的状态是resolved    // 则then中的函数会执行,    // 且obj1的值会传给rs}).catch(function(rs){    // 如果p1的状态是rejected    // 则catch中的函数会执行,    // 且obj2的值会传给rs}).finally(function(){    // 一定会执行的函数})

在构造器的函数体中,一旦状态发生了变化,就会进行then,或者是catch中去,同时把promiseValue传入对应的函数。

具体来说:

•状态从pending变成resolved,进入then中,调用函数,并传入此时的promiseValue(就是调用resolve时传入的实参)•状态从pending变成rejected,进入catch中,调用函数,并传入此时的promiseValue(就是调用reject时传入的实参)

执行流程图:

根据实际情况的需要,也可以不加上finally()。

6 使用promise改造回调函数

下面我们回到具体的问题。如何对现有的,通过回调函数来实现异步操作的代码进行改写。

基本步骤有三步:

第一步:建立模板。这里的模板是指固定的套路:写一个空函数,在函数体中的创建一个promise对象,并返回。

function fnName(){    
        var p = new Promise((resolve,reject)=>{})    
        return p;
}

当然, 如果使用箭头函数的话,还可以简化成这样:

let fnName = () => new Promise((resolve,reject)=>{})

第二步:把异步功能写入构造器中,根据实际来决定是否调用resolve,reject。

function fnName(){     
    var p = new Promise((resolve,reject)=>{    
        // 这里写具体的代码,    
        // 并在某个恰当的时机去调用resolve和reject函数。 
    })     
    return p;
}

第三步:调用函数。

通过fnName().then().catch() 结构来调用这个函数,会在异步操作成功时挪then中的函数,在失败时执行catch中的函数。

下面来改写两个小dome。

6.1 示例1 ajax

原回调函数的写法

var xhr = new XMLHttpRequest();xhr.open('get','http://httpbin.org/ip')xhr.onload = function() {    console.log(xhr.responseText)    }xhr.onerror = function (){    console.log('请求接口错误')}xhr.send()

改写之后

let ajax = function() {  return new Promise((resolve,reject)=>{    var xhr = new XMLHttpRequest();    xhr.open('get','http://httpbin.org/ip')    xhr.onload = function() {      resolve(xhr.responseText)    }    xhr.onerror = function (){      reject('请求接口错误')    }    xhr.send()  })}// 注意下面 then catch用法ajax().then(res =>{    console.log(res)}).catch(err =>{    console.log(err)})

具体的参数封装并不没有

这里用到的接口http://httpbin.org/ip是从http.org[2] 上访问的,它的功能是返回你本机的ip地址。

6.2 示例2 nodejs中的文件读取

原来的写法

const fs = require('fs');
fs.readFile("1.txt", 'utf8', (err, data) => {    
    if (err) {
        reject(err);    
    } else {        
        resolve(data);    
    }});

改写之后

const fs = require('fs');
function readFile(filename) {  
    return new Promise((resolve, reject) => {    
                fs.readFile(filename, 'utf8', (err, data) => {      
                    if (err) {        
                        reject(err);      
                    } else {        
                        resolve(data);      
                    }    
                });  
    })
}
readFile('./server.js1').then(rs => {   console.log(rs); }) .catch(err => {   console.log('err', err); })

7 小结

本文介绍了Promise的基本用法,并分析了promise经典格式的执行流程,最后给出了用promise去改写异步代码的套路