Promise(异步处理)

1,574 阅读9分钟

本文首发CSDN

什么是promise


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

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

  1. 没有异步就不需要promise
  2. promise本身不是异步,只是我们去编写异步代码的一种方式

我们知道JavaScript中所有代码都是单线程执行的,所以导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。

异步执行可以用回调函数实现,功能没有任何问题,但是这个写法比较讨厌--- 这就是臭名昭著的回调地狱(callback hell)。 针对这个写法,非常的糟糕,需要有一种机制,让这个写法变得更加优雅一些。

所以,在前端社区中,就有一些牛人,纷纷提供了一些新的写法,以避免回调地狱。 后来,es6就在这些基础之上,将promise直接纳入的规范。从而成为es6中的标准用法

promise A+规范


由于promise本身出自于民间,就有很多不同的实现版本。 Es6将promise纳入自己规范的时候,也遵循了一个相应的标准 -- promise A+规范。

将其归纳为4321规范。

  • 4:4大术语
  • 3:3种状态
  • 2:2种事件
  • 1:1个对象

4大术语

一定要结合异步操作来理解。 既然是异步,这个操作需要有个等待的过程,从操作开始,到获取结果,有一个过程的。

解决(fulfill):指一个 promise 成功时进行的一系列操作,如状态的改变、回调的执行。虽然规范中用 fulfill 来表示解决,但在后世的 promise 实现多以 resolve 来指代之。

拒绝(reject):指一个 promise 失败时进行的一系列操作。

终值(eventual value):所谓终值,指的是 promise 被解决时传递给解决回调的值,由于 promise 有一次性的特征,因此当这个值被传递时,标志着 promise 等待态的结束,故称之终值,有时也直接简称为值(value)。

据因(reason):也就是拒绝原因,指在 promise 被拒绝时传递给拒绝回调的值。

3种状态

当操作完成时,就有相应的结果,结果有两种:

  • 成功了
  • 失败了

一共是3种状态,如下:

  • 等待态(Pending)
  • 执行态(Fulfilled)
  • 拒绝态(Rejected)

针对每一种状态,有一些规范:

等待态(Pending) 处于等待态时,promise 需满足以下条件:

  • 可以迁移至执行态或拒绝态

执行态(Fulfilled) 处于执行态时,promise 需满足以下条件:

  • 不能迁移至其他任何状态
  • 必须拥有一个不可变的终值

拒绝态(Rejected) 处于拒绝态时,promise 需满足以下条件:

  • 不能迁移至其他任何状态
  • 必须拥有一个不可变的据因

2种事件

针对3种状态,只有如下两种转换方向:

  • pending --> fulfilled
  • pendeing --> rejected

在状态转换的时候,就会触发事件。

  • 如果是pending --> fulfiied,就会触发onFulFilled事件
  • 如果是pendeing --> rejected,就会触发onRejected事件

1个对象

就是指promise对象

promise使用


在es6中,提供了promise构造器,这个promise构造器,必须要传递一个参数。

构造器的参数,是一个回调函数,包含两个参数: resolve , reject

promise实例化

实例化

let a = new promise((resolve, reject) => {
    // resolve("ok") 
    reject("NOK")
})

当前这个对象处于 pending 状态。

回调函数中的两个参数,其作用就是用于转换状态:

resolve ,将状态从pending --> fullFilledreject ,将状态从pending --> rejected

直接使用函数调用的方式来进行转换,在转换的时候必须要传递相应参数

  • Resolve函数的参数,就是指 终值(value)
  • Reject函数的参数,就是指 据因(reason)

then方法

let a = new Promise((resolve, reject) => {
    resolve("ok")  // 终值
    reject("NOK")   //据因
})

怎么获取这里的终值和据因呢?

引入事件机制,三种状态,只有两种转换方向

在状态转换的时候,就会触发事件。

如果是pending --> fulfiied,就会触发onFulFilled事件 如果是pendeing --> rejected,就会触发onRejected事件

在调用resolve方法或者reject方法的时候,就一定会触发事件。 需要注册onFulFilled事件 和 onRejected事件

针对事件的注册,Promise对象提供了then方法,如下:

promise.then(onFulFilled,onRejected)
a.then(function (res) {
    console.log("ok")  
}, function (err) {
        console.log("NOK")  
})

catch方法

promise then方法也有语法糖 --->> catch

如上,then方法需要写两个回调方法,promise.then(onFulFilled,onRejected),辨识度不高,所以promise提供了一个catch方法,用于注册 onRejected回调。

let a = new Promise((resolve, reject) => {
    if (1) {
        resolve("开始")
    } else {
        reject("结束")
    }
})

a.then(data => console.log(data + 'start'));
a.catch(err => console.log(err + 'end'));

catch其实是then的简写,then(null,callback)

a.then(data => console.log(data + 'start'));

a.then(null, err => console.log(err + 'start'));
//a.catch(err => console.log(err + 'end'));

then方法调用之后,仍然返回的是promise对象,所以可以链式调用

a.then(data => console.log(data)).catch(err => console.log(err))

所以,我们在使用promise对象时,一般这么描述,异步操作成功的时候,走then,失败的时候就走catch。

then方法链式调用

then方法的链式调用非常有趣,简单的说,在then方法后面继续的调用then方法,这就是then的链式调用

let fs = require("fs")
function readFile(...args){
    return new Promise((resolve,reject)=>{
        fs.readFile(...args,function(err,data){
            if(err) reject(err)
            resolve(data)
        })
    });
}
// 如果返回的是一个promise,那么这个promise会执行,并且会采用它的状态
readFile("./name.txt","utf8").then(data=>{
    // return data;   return readFile(data,"utf8")
    return readFile(data,"utf8")
},err=>{
    return err;
}).then(data=>{
    console.log(data)  // 666
},err=>{
    console.log(err)
})
// 链式调用  如果在上一个then的第一个函数中,返回一个普通值,会走到下一个then的第1个函数,return的值作为这个then的data

// 如果返回的是一个promise,会作为下一个then的promise对象,data err去promise对象中取

then方法返回的是一个promise对象,但它是一个新的promise对象,不再是原来的promise对象

前一个then方法的返回值,或作为下一个then方法的参数(普通参数,promise对象例外)

如果返回的是promise对象,就作为下一次调用then方法的promise对象

then方法中回调函数的return 返回值,和then方法本身的返回值是两码事

可以在后面添加一个catch方法,链式调用then方法时,有错直接不再继续并执行错误处理函数。

promise定义典例


一点简单栗子直观感受一下

读取文件

//const fs = require('fs')
//var rs = new Promise((resolve, reject) => {
//    fs.readFile('name.txt', 'utf8', (err, data) => {
//        if (err) {
//            reject(err)
//        } else {
//            resolve(data)
//        }
//    })
//})
//rs.then(data => console.log(data))
//    .catch(err => console.log(err))

//在开发的时候,我们定义promise对象,必须是要作为函数的返回值。

const fs = require('fs')
function readFile(file) {
    return new Promise((resolve, reject) => {
        fs.readFile(file, 'utf8', (err, data) => {
            if (err) {
                reject(err)
            } else {
                resolve(data)
            }
        })
    })
}
readFile('name.txt').then(data => console.log(data))
    .catch(err => console.log(err))

根据随机数返回结果

var rs = new Promise((resolve, reject) => {
    setTimeout(() => {
        var n = Math.random()
        console.log(n)
        if (n > 0.5) {
            resolve("n比较大")
        } else {
            reject("n比较小")
        }
    }, 1000)
})
rs.then(res => console.log(res)).catch(err => console.log(err))

清晰明了,promise在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了

all 和 race 方法

除了串行执行若干异步任务外,Promise还可以并行执行异步任务,可以使用Promise.all()方法和Promise.race() 方法。

并行执行两个任务,如果有任何一个失败,就不进行,用Promise.all()

var p1 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 400, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 600, 'P2');
});
// 同时执行p1和p2,并在它们都完成后才执行then:
Promise.all([p1, p2]).then(function (results) {
    console.log(results); // 获得一个Array: ['P1', 'P2']
});

有些时候,多个异步任务是为了容错,即开多个任务可以得到相同的结果,只需要获得先返回的结果即可,用Promise.race()

var p1 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 400, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 600, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
    console.log(result); // 'P1'
});
// 由于p1执行较快,Promise的then()将获得结果'P1'。p2仍在继续执行,但执行结果将被丢弃。

一点小cones

思考:catch方法放在then方法前会发生什么? 还是用上面的栗子

var rs = new Promise((resolve, reject) => {
       setTimeout(() => {
           var n = Math.random()
           console.log(n)
           if (n > 0.5) {
               resolve("n比较大")
           } else {
               reject("n比较小")
           }
       }, 1000)
   })
rs.catch(err => console.log(err)).then(res => console.log(res))
// rs.then(res => console.log(res)).catch(err => console.log(err))

好,第一次Math.random()返回了一个n>0.5 的数,执行then()

结果:n比较大

再来,终于Math.random()返回一个n<0.5 的数

结果:n比较小,undefined

Promise 不论成功或失败都会调用 then 然而catch() 只有当 promise 失败时才会调用

因为 catch 的回调函数没有返回值,所以 catch 方法返回的 promise 对象的状态变为了 'resolved',value 值变为了 undefined

Promise().then().then....catch() 多任务串行执行.

情景化记忆:在一个任务链中,比如我要向上级部门一层层的往上提交申请,if(某种条件)承诺帮你resolve解决问题,else承诺reject你的请求. 他给出的resolve问题的办法只是个空头Promise,then到总经理那实现具体承诺,如果总经理还是给一个空头承诺(返回Promise实例),还得then到董事长那里.... 任一一步做出的是reject的承诺,还有什么好说的,被拒绝了,后面的就不会再往上走了呀. 准备catch 拒绝通知吧blablabla

Promise.all([p1,p2,...]) 多任务并行执行都要成功才进入then,返回结果数组.

Promise.race([p1,p2,...])多任务赛跑then()和catch(),谁先调用算谁的,其它任务中断.