likes
comments
collection
share

前端初学者必学(promise的初使用)

作者站长头像
站长
· 阅读数 12

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);
});

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

4 Promise构造器

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

4.1 Promise的格式

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

前端初学者必学(promise的初使用)

要点:

•构造器必须要给定一个参数,如果不给就是会报错。例如,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浏览器中的结果如下:

前端初学者必学(promise的初使用)

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

前端初学者必学(promise的初使用)

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

4.2.3 rejected

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

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

前端初学者必学(promise的初使用)

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。
console.dir(p)

前端初学者必学(promise的初使用)

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

前端初学者必学(promise的初使用)

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

单独来看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时传入的实参)

执行流程图:

前端初学者必学(promise的初使用)

根据实际情况的需要,也可以不加上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 小小的面试题

代码填空,并完成指定功能。

function sleep(time){
  // 请写出你的代码
}

sleep(2000).then(()=>{
   console.log("后续操作")
})
console.log(2);

目标是让sleep 的功能与setTimeout一样:就是等2000毫秒之后再执行后续操作。

参考代码如下:

function sleep(time){
  return new Promise(function(resolve,reject){
    setTimeout(function(){
        resolve()
    }, time)
  });
}

8 小结

本文介绍了Promise的基本用法,并分析了promise经典格式的执行流程,最后给出了用promise去改写异步代码的套路,并予以实践,掌握这些,就应该可以简单用起来了吧。

关于它的链式写法,以及解决回调地狱的问题在下篇说明。