主要内容:

  • 回调函数
  • 什么是Promise
  • Promise的基本用法
  • Promise.prototype.then() 和 Promise.prototype.catch()
  • Promise.resolve() / Promise.reject()
  • Promise.all()
  • Promise.race()

1、回调函数

简单说回调方法就是将一个方法func2作为参数传入另一个方法func1中,当func1执行到某一步或者满足某种条件的时候才执行传入的参数func2,如下面的代码段

// 当参数a大于10且参数func2是一个方法时 执行func2
function func1(a, func2) {
    if (a > 10 && typeof func2 == 'function') {
        func2()
    }
}

func1(11, function() {
    console.log('this is a callback')
})

在过去写异步代码都要靠回调函数,当异步操作依赖于其他异步操作的返回值时,会出现一种现象,被程序员称为 “回调地狱”,比如这样 :

// 假设我们要请求用户数据信息,它接收两个回调,假设我们要请求用户数据信息,它接收两个回调,successCallback 和 errCallback
function getUserInfo (successCallback, errCallback) {
    $.ajax({
        url : 'xxx',
        method : 'get',
        data : {
            user_id : '123'
        },
        success : function(res) {
            successCallback(res)    // 请求成功,执行successCallback()回调
        },
        error : function(err) {
            errCallback(err)        // 请求失败,执行errCallback()回调
        }
    })
}

骗我 ? 这哪里复杂了,明明很简单啊,说好的回调地狱呢 ? 不急,继续看
假设我们拿到了用户信息,但是我们还要拿到该用户的聊天列表,然后再拿到跟某一个人的聊天记录呢 ?

// getUserInfo -> getConnectList -> getOneManConnect()

getUserInfo((res)=>{
    getConnectList(res.user_id, (list)=>{
        getOneManConnect(list.one_man_id, (message)=>{
            console.log('这是我和某人的聊天记录')
        }, (msg_err)=>{
            console.log('获取详情失败')
        })
    }, (list_err)=>{
        console.log('获取列表失败')
    })
}, (user_err)=>{
    console.log('获取用户个人信息失败')
})

大兄弟,刺激不,三层嵌套,再多来几个嵌套,就是 “回调地狱” 了。这时候,promise来了。

2、什么是Promise

Promise是异步编程的一种解决方案,它有三种状态,分别是pending-进行中、resolved-已完成、rejected-已失败。

当Promise的状态又pending转变为resolved或rejected时,会执行相应的方法,并且状态一旦改变,就无法再次改变状态,这也是它名字promise-承诺的由来。

形象例子来说明promise:

// 定外卖就是一个Promise,Promise的意思就是承诺
// 我们定完外卖,饭不会立即到我们手中
// 这时候我们和商家就要达成一个承诺
// 在未来,不管饭是做好了还是烧糊了,都会给我们一个答复
function 定外卖(){
    // Promise 接受两个参数
    // resolve: 异步事件成功时调用(菜烧好了)
    // reject: 异步事件失败时调用(菜烧糊了)
    return new Promise((resolve, reject) => {
        let result = 做饭()
    // 下面商家给出承诺,不管烧没烧好,都会告诉你
    if (result == '菜烧好了') 
        // 商家给出了反馈
        resolve('我们的外卖正在给您派送了')
    else 
        reject('不好意思,我们菜烧糊了,您再等一会')
    })
}

// 商家厨房做饭,模拟概率事件
function 做饭() {
    return Math.random() > 0.5 ? '菜烧好了' : '菜烧糊了'
}

// 你在家上饿了么定外卖
// 有一半的概率会把你的饭烧糊了
// 好在有承诺,他还是会告诉你

定外卖()
    // 菜烧好执行,返回'我们的外卖正在给您派送了'
    .then(res => console.log(res))
    // 菜烧糊了执行,返回'不好意思,我们菜烧糊了,您再等一会'
    .catch(res => console.log(res))

ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。
下面代码创造了一个Promise实例。

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

promise.then(function(value) {//resolved状态的回调函数
  // success
}, function(error) {//rejected状态的回调函数
  // failure
});

then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。

下面是一个Promise对象的简单例子。

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms, 'done');//'done'是resolve的参数
  });
}

timeout(100).then((value) => {
  console.log(value);
});

上面代码中,timeout方法返回一个Promise实例,表示一段时间以后才会发生的结果。过了指定的时间(ms参数)以后,Promise实例的状态变为resolved,就会触发then方法绑定的回调函数。


扩展内容:setTimeout不只有两个参数!
定时器启动时候,第三个以后的参数是作为第一个func()的参数传进去。

function sum(x, y) {
    console.log(x+y) //隔一秒钟输出3
}
setTimeout(sum, 1000, 1, 3);

Promise 新建后就会立即执行。

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hi!');

// Promise
// Hi!
// resolved

上面代码中,Promise 新建后立即执行,所以首先输出的是Promise。然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved最后输出。

再看下面的例子:

new Promise((resolve, reject) => {
  resolve(1);
  console.log(2);
}).then(r => {
  console.log(r);
});
// 2
// 1

上面代码中,调用resolve(1)以后,后面的console.log(2)还是会执行,并且会首先打印出来。这是因为立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。

一般来说,调用resolve或reject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。

new Promise((resolve, reject) => {
  return resolve(1);
  // 后面的语句不会执行
  console.log(2);
})

3、Promise的基本用法

// 方法1
let promise = new Promise ((resolve, reject) => {
    if (success) {
        resolve(a); //成功的时候调用resolve(),pending ——> resolved 参数将传递给对应的回调方法
    } else {
        reject(err); //失败的时候调用rejec(),pending ——> rejectd
    }
})

// 方法2
function createPromise () {
    return new Promise (function (resolve, reject) {
        if ( success ) {
            resolve(a)
        } else {
            reject(err)
        }
    })
}

4、Promise.prototype.then() 和 Promise.prototype.catch()

.then()方法使Promise原型链上的方法,它包含两个参数方法,分别是已成功resolved的回调和已失败rejected的回调。

promise.then(
    () => { console.log('this is success callback') },
    () => { console.log('this is fail callback') }
)

.catch()的作用是捕获Promise的错误,与then()的rejected回调作用几乎一致。但是由于Promise的抛错具有冒泡性质,能够不断传递,这样就能够在下一个catch()中统一处理这些错误。同时catch()也能够捕获then()中抛出的错误,所以建议不要使用then()的rejected回调,而是统一使用catch()来处理错误。

promise.then(
    () => { console.log('this is success callback') }
).catch(
    (err) => { console.log(err) }
)

同样,catch()中也可以抛出错误,由于抛出的错误会在下一个catch中被捕获处理,因此可以再添加catch()。

使用rejects()方法改变状态和抛出错误 throw new Error() 的作用是相同的。

当状态已经改变为resolved后,即使抛出错误,也不会触发then()的错误回调或者catch()方法。

then() 和 catch() 都会返回一个新的Promise对象,可以链式调用:

promise.then(
    () => { console.log('this is success callback') }
).catch(
    (err) => { console.log(err) }
).then(
    ...
).catch(
    ...
)

5、Promise.resolve() / Promise.reject()

用来包装一个现有对象,将其转变为Promise对象,但Promise.resolve()会根据参数情况返回不同的Promise:

参数是Promise:原样返回。
参数带有then方法:转换为Promise后立即执行then方法。
参数不带then方法、不是对象或没有参数:返回resolved状态的Promise。

Promise.reject()会直接返回rejected状态的Promise。

6、Promise.all()

参数为Promise对象数组,如果有不是Promise的对象,将会先通过上面的Promise.resolve()方法转换

var promise = Promise.all( [p1, p2, p3] )
promise.then(
    ...
).catch(
    ...
)

当p1、p2、p3的状态都变成resolved时,promise才会变成resolved,并调用then()的已完成回调,但只要有一个变成rejected状态,promise就会立刻变成rejected状态。

7、Promise.race()

“竞速”方法,参数与Promise.all()相同,不同的是,参数中的p1、p2、p3只要有一个改变状态,promise就会立刻变成相同的状态并执行对应的回调。

var promise = Promise.race( [p1, p2, p3] )
promise.then(
    ...
).catch(
    ...
)