JS异步编程:Promise使用方法梳理

638 阅读13分钟

Promise-MDN

总结一下Promise的基本使用和深入使用,为学习Promise源码打下坚实的基础

基本使用

Promise 是 JS 中进行异步编程的新解决方案(旧方案是单纯使用回调函数)

  • 从语法上来说: Promise 是一个构造函数
  • 从功能上来说: Promise 对象用来封装一个异步操作并可以获取其成功/失败的结果值

为什么要用 Promise

  1. 指定回调函数的方式更加灵活。没有 Promise 之前必须在启动异步任务时同时指定回调函数。Promise是先启动异步任务,然后返回Promise对象,再给Promise对象绑定回调函数。在异步任务结束后也可以指定多个回调函数

  2. 支持链式调用, 可以解决回调地狱(回调函数中嵌套回调函数)问题。出现回调地狱,不便于阅读也不便于异常处

    image。png

基本使用流程

基本编码流程

// 1) 创建 Promise 对象(pending 状态), 指定执行器函数
const p = new Promise((resolve, reject) => {
  // 2) 在执行器函数中启动异步任务
  setTimeout(() => {
    const time = Date.now();
    // 3) 根据结果做不同处理
    // 3.1) 如果成功了, 调用 resolve(), 指定成功的 value, 变为 resolved 状态;
    if (time % 2 === 1) {
      resolve("成功的值 " + time);
    } else {
      // 3.2) 如果失败了, 调用 reject(), 指定失败的 reason, 变为rejected 状态
      reject("失败的值" + time);
    }
  }, 2000); 
});
// 4) 能 Promise 指定成功或失败的回调函数来获取成功的 vlaue 或失败的 reason
p.then(
  (value) => {
    // 成功的回调函数 onResolved, 得到成功的 vlaue
    console.log("成功的 value: ", value);
  },
  (reason) => {
    // 失败的回调函数 onRejected, 得到失败的 reason
    console.log("失败的 reason: ", reason);
  }
);

Promise 对象 状态 属性

Promise 的状态是实例对象中的一个属性 『PromiseState』

如果我们将 Promise 的实例打印出来:

image。png

他有三个值

  • 'pending' 未决定的
  • 'resolved' / 'fullfilled' 成功
  • 'rejected' 失败 并且改变只存在两种可能。只能像下面这样变化:
  1. 'pending' 变为'resolved'
  2. 'pending' 变为'rejected'

说明:只有这2种改变方式,且一个 Promise 对象只能改变一次。 无论变为成功还是失败,都会有一个结果数据。成功的结果数据一般称为value,失败的结果数据一般称为reason

Promise 对象的 属性

这是实例对象中的另一个属性 『PromiseResult』

保存着异步任务『成功/失败』的结果,只有 resolvereject 函数可以改变这个结果

image。png

Promise 构造函数

Promise 构造函数: Promise (excutor) {}

  1. executor 函数: 执行器 (resolve, reject) => {}
  2. 执行器中的 resolve 函数: 内部定义成功时调用的函数 value => {}
  3. 执行器中的 reject 函数: 内部定义失败时调用的函数 reason => {}

说明: executor 会在 Promise 内部立即同步调用,异步操作在执行器中执行

let p = new Promise((resolve, reject) => {
             // 同步调用
             console.log(111);
        });
    console.log(222);

先输出111,后输出222

属性区分

[[ ]]包裹起来的属性叫做ECMAScript官方规范中叫做内置属性.浏览器内部的这些属性不让用户自己操作,但可以暴露出来给我们看

image.png

公共属性

image.png

Promise.prototype.then 方法

Promise.prototype.then 方法: (onResolved, onRejected) => {}

  • onResolved 函数: 成功的回调函数 (value) => {}
  • onRejected 函数: 失败的回调函数 (reason) => {} 说明: 指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调
  • 返回一个新的 Promise 对象

Promise.prototype.catch 方法

Promise.prototype.catch 方法: (onRejected) => {}

onRejected 函数: 失败的回调函数 (reason) => {}

说明: 内部是对 then 的一个封装, then() 的语法糖, 相当于: then(undefined, onRejected)

let p = new Promise((resolve, reject) => {
    //修改 Promise 对象的状态
    reject('error');
});
//执行 catch 方法
p.catch(reason => {
    console.log(reason);
});

Promise.resolve 方法

Promise.resolve 方法: (value) => {}Promise 构造函数上的方法

  • value : 成功的数据或 Promise 对象

说明: 返回一个成功/失败的 Promise 对象

image。png

注意

  • 如果传入的参数为 非 Promise 类型的对象, 则返回的结果为成功 Promise 对象
  • 如果传入的参数为 Promise 对象, 则参数的结果决定了 resolve 的结果
let p2 = Promise.resolve(new Promise((resolve, reject) => {
   resolve('OK');
}));
 console.log(p2);

image。png

let p2 = Promise.resolve(new Promise((resolve, reject) => {
  reject('Error');
}));
console.log(p2);
p2.catch(reason => {
  console.log(reason);
})

image。png

tip: 如果有一个失败的Promise,而且我们还没有对失败的结果做处理,那么就会抛出错误

let p2 = Promise.resolve(new Promise((resolve, reject) => {
  reject('Error');
}));//或者p2 = Promise.reject()

image。png

Promise.reject

Promise.reject 方法: (reason) => {} 也是构造函数上的方法

  • reason : 失败的原因 说明: 返回一个失败的 Promise 对象

注意:不管接受什么参数,返回的都是失败的 Promise 对象(即使是成功的 Promise 对象),返回的结果状态是 reject ,值就是传入的那个值,即返回的结果永远是失败的,而且传入什么值就返回什么值

let p3 = Promise.reject(new Promise((resolve, reject) => {
    resolve('OK');
}));

console.log(p3);

image。png

Promise.all 方法

Promise.all 方法: (Promises) => {}

  • Promises : 包含 n 个 Promise 的数组

说明: 返回一个新的 Promise , 只有所有的 Promise 都成功才成功,成功的结果就是所有Promise成功返回的结果组成的数组。只要有一个失败了就直接失败。

let p1 = new Promise((resolve, reject) => {
  resolve('OK');
})
let p2 = Promise.resolve('Success');
let p3 = Promise.resolve('Oh Yeah');

const result = Promise.all([p1, p2, p3]);
console.log(result);

image。png

let p1 = new Promise((resolve, reject) => {
  resolve('OK');
})
let p2 = Promise.reject('Error');
let p3 = Promise.resolve('Oh Yeah');

const result = Promise.all([p1, p2, p3]);
console.log(result);

image。png

Promise.race 方法

Promise.race 方法: (Promises) => {} race 本身的意思是赛跑的意思

Promises : 包含 n 个 Promise 的数组 说明: 返回一个新的 Promise第一个完成Promise 的结果状态(不管成功还是失败)就是最终的结果状态

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
      resolve('OK');
  }, 1000);
})
let p2 = Promise.resolve('Success');
let p3 = Promise.resolve('Oh Yeah');

//调用
const result = Promise.race([p1, p2, p3]);

console.log(result);

image。png 响应p2,p2首先完成(记住不管是成功还是失败)

基本流程

image。png

两个基本使用的例子

封装一个读取文件函数

/**
 * 封装一个函数 mineReadFile 读取文件内容
 * 参数:  path  文件路径
 * 返回:  Promise 对象
 */
function mineReadFile(path){
    return new Promise((resolve, reject) => {
        //读取文件
        require('fs').readFile(path, (err, data) =>{
            //判断
            if(err) reject(err);
            //成功
            resolve(data);
        });
    });
}

mineReadFile('./resource/content.txt')
.then(value=>{
    //输出文件内容
    console.log(value.toString());
}, reason=>{
    console.log(reason);
});

封装Ajax

/**
 * 封装一个函数 sendAJAX 发送 GET AJAX 请求
 * 参数   URL
 * 返回结果 Promise 对象
 */
function sendAJAX(url){
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.responseType = 'json';
        xhr.open("GET", url);
        xhr.send();
        //处理结果
        xhr.onreadystatechange = function(){
            if(xhr.readyState === 4){
                //判断成功
                if(xhr.status >= 200 && xhr.status < 300){
                    //成功的结果
                    resolve(xhr.response);
                }else{
                    reject(xhr.status);
                }
            }
        }
    });
}

sendAJAX('https://api.apiopen.top/getJok')
.then(value => {
    console.log(value);
}, reason => {
    console.warn(reason);
});

util.promisify

util.promisify 传入一个遵循常见的错误优先的回调风格的函数(即以 (err, value) => ... 回调作为最后一个参数),并返回一个返回 Promise 的版本。 就是可以不用自己封装,直接返回 Promise

/**
 * util.promisify 方法
 */
//引入 util 模块
const util = require('util');
//引入 fs 模块
const fs = require('fs');
//返回一个新的函数
let mineReadFile = util.promisify(fs.readFile);

mineReadFile('./resource/content.txt').then(value=>{
    console.log(value.toString());
});

深入使用(非常关键的细节问题)

如何修改Promise对象状态

pending->成功

pending => fulfilled (resolved)

let p = new Promise((resolve, reject) => {
  //1. resolve 函数
  // resolve('ok'); // pending   => fulfilled (resolved)
});

pending ->失败

pending => rejected

两种方式:第一种 reject

let p = new Promise((resolve, reject) => {
  //2. reject 函数
  reject("error");// pending  =>  rejected 
});

第二种: throw 关键字抛出错误

let p = new Promise((resolve, reject) => {
  throw '出问题了';
});
console.log(p)

image。png

let p = new Promise((resolve, reject) => {
  throw new Error('出问题了');
});
console.log(p)

image。png

可指定多个回调

一个 Promise 指定多个成功/失败回调函数, 当 Promise 改变为对应状态时都会调用

let p = new Promise((resolve, reject) => {
  // resolve('OK');
});

///指定回调 - 1
p.then(value => {
  console.log(value);
});

//指定回调 - 2
p.then(value => {
  alert(value);
});

如果没有改变状态,回调函数就不会执行

改变状态与指定回调顺序问题

resolve 改变状态方法先执行,还是 then 指定回调方法先执行?

都有可能, 正常情况下是先指定回调再改变状态(异步任务), 但也可以先改状态再指定回调(同步任务)

  1. 先改状态再指定回调( resolve()reject() 先执行, then() 后执行)

    直接调用 resolve()/reject() ,当执行器中的任务是同步任务的时候,那就会先改变 Promise 的状态,在指定回调

    let p = new Promise((resolve, reject) => {
      resolve('OK');
    });
    p.then(value => {
      console.log(value);
    },reason=>{
    
    })
    

    执行顺序是这样:先执行 resolve('OK') ,再执行 p.then() ,再执行 then() 中的回调函数

  2. 先指定回调再改变状态( then 先执行, resolvereject 后执行)

    当执行器函数当中是异步任务, then 方法先执行,改变状态后执行,那也是可以的。改变状态之后, then 中的回调函数才会执行

    let p = new Promise((resolve, reject) => {
      setTimeout(() => {
          resolve('OK');
      }, 1000);
    });
    
    p.then(value => {
      console.log(value);
    },reason=>{
    
    })
    

    执行顺序是这样:先执行 p.then() ,再执行 resolve('OK') ,再执行 then() 中的回调函数

Promise 执行器中支持同步任务也支持异步任务,在使用 Promise 的时候,后一种方式出现的比较多

then 方法的返回结果

看一个问题

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
      resolve('OK');
  }, 1000);
});
let result = p.then(value => {
  console.log(value);
},reason=>{
  console.log(reason);
})
console.log(result)

Promise.then() 返回的新 Promise image。png

那新的 Promise 实例的结果状态和值由什么决定?

新的 Promise 实例的结果状态和值,由 then()指定的回调函数执行的结果决定

回调函数执行的结果具体有以下几种情况.

注意:两个回调函数只会执行一个且不管执行哪个,都适合下面的情况

  1. 如果抛出异常 ,新 Promise 状态变为 rejected reason 为抛出的异常。

    let p = new Promise((resolve, reject) => {
     resolve('ok');
    });
    //执行 then 方法
    let result = p.then(value => {
     //1. 抛出错误
     throw '出了问题';
    }, reason => {
     console.warn(reason);
    });
    console.log(result)
    

    image。png

  2. 如果返回的是Promise 的任意值, 新 Promise 状态变为 resolved value 为返回的值(函数不写return关键字,默认返回 undefined

    let p = new Promise((resolve, reject) => {
    resolve('ok');
    });
    //执行 then 方法
    let result = p.then(value => {
       //2. 返回结果是非 Promise 类型的对象
    }, reason => {
    });
    console.log(result)
    

    image.png

    let p = new Promise((resolve, reject) => {
     resolve('ok');
    });
    //执行 then 方法
    let result = p.then(value => {
     //2. 返回结果是非 Promise 类型的对象
     return 521;
    }, reason => {
     console.warn(reason);
    });
    console.log(result)
    

    image。png

    注意!!

    let p = new Promise((resolve, reject) => {
    // 一个reject 的Promise实例
    reject('fail');
    });
    let result = p.then(value => {
        return 'resolve返回了';
    }, reason => {
        console.warn(reason);
        return 'reject返回了';
    });
    console.log('result',result)
    

    image.png

  3. 如果返回的是另一个Promise, 此 Promise 的结果就会成为then返回的那个新 Promise 的结果。状态都和新 Promise 的结果对应

    let p = new Promise((resolve, reject) => {
     resolve("ok");
    });
    //执行 then 方法
    let result = p.then(
     (value) => {
       //3. 返回结果是 Promise 对象
       return new Promise((resolve, reject) => {
         resolve('success');
         //reject("error");
       });
     },
     (reason) => {
       console.warn(reason);
     }
    );
    console.log(result);
    

    resolve:

    image。png

    reject:

    image。png

总结: 两个回调函数不管其中哪一个执行,then()返回的实例都遵循一下规则:

  1. 如果返回一个普通的值,或者返回一个'resolved'状态的Promise实例,那么状态就是resolved,值就是返回的值或者resolve的值
  2. 如果抛出错误,或者return返回'rejected'状态的Promise实例,那么值就是抛出的错误或者rejecte的值

链式调用串联多个任务

因为 then 返回的结果是新的 Promise 对象,所以我们可以用 then 串联起多个 Promise 任务

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
      resolve('OK');
  }, 1000);
});

p.then(value => {
   console.log(value)
  return new Promise((resolve, reject) => {
      resolve("success");
  });
}).then(value => {
  console.log(value);
})

一秒后打印出 'ok''success'

这样我们可以把多个任务都写成 Promise 的形式,然后将任务串联起来。

注意:如果 then() 中的回调函数什么都不返回,最后会默认返回 undefined

let p = new Promise((resolve, reject) => {
    resolve('ok')
});

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

image。png

第一个 then 返回的就是一个成功的 Promise ,其状态为 resolve ,值为 OK

第一个 then 返回的也是一个成功的 Promise ,其状态为 resolve ,值为上一个 then 的回调函数返回的结果 'undefined'

通过 then 的链式调用串连多个同步/异步任务

注意链式调用Promise的返回值变化

可以看下面的特殊的例子

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('success')
    }, 10);
});
p.then(value => {
    console.log('111value:',value);
    return value
}).then(value => {
    console.log('222value:',value);
    throw '失败啦!';
}).then(value => {
        console.log('333value:',value);
        return '333resolve'
    }, reason => {
        console.warn('333reason:',reason);
        return '333reject'
}).then((value) => {
        console.log('444value', value)
    },reason => {
        console.warn('444reason',reason);
})

image.png

可以看到即使中间resolve了或者抛出错误Promise链仍然不会中断。 并且可以看到第三个then执行的是第二个onReject回调函数,但是在第四个then仍然接收的是一个'resolve'状态的Promise。因为then()函数中的两个回调,只有在抛出错误返回'reject'状态的Promise实例then()函数返回的才是'reject'状态的Promise实例。这一点在上面then()的返回值 章节 里已经阐述。

因为catch仅仅是then的语法糖,所以catch也会返回一个Promise,等同于.then(undefined,(reason =>{}))

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('success')
    }, 10);
});
p.then(value => {
    console.log('111value:',value);
    return value
}).then(value => {
    console.log('222value:',value);
    throw '失败啦!';
}).catch(reason => {
    console.warn('333reason:',reason);
}).then((value) => {
    console.log('444value', value)
},reason => {
    console.warn('444reason',reason);
})

image.png

输出和上面一样

异常穿透

异常穿透:当使用 Promisethen 链式调用时, 可以在最后指定失败的回调。 前面任何操作出了异常, 都会传到最后失败的回调中处理。

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
      reject('Err');
  }, 1000);
});

p.then(value => {
  console.log(111);
}).then(value => {
  console.log(222);
}).then(value => {
  console.log(333);
}).catch(reason => {
  console.warn(reason);
});

输出的是第一个Promise的值 image。png

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('OK');
  }, 1000);
});

p.then(value => {
  console.log(111);
}).then(value => {
  
  console.log(222);
  throw '失败啦!';
}).then(value => {
  console.log(333);
}).catch(reason => {
  console.warn(reason);
});

捕获链中遇到的 reject image。png

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('Err');
    }, 10);
});

p.then(value => {
    console.log(111);
}).then(value => {
    console.log(222);
}).then(value => {
    console.log(333);
}).catch(reason => {
    console.warn(reason);
}).then(value => {
    console.log('444resolve');
},reason=>{
    console.log('444reject');
})

image.png catch完之后,返回的仍然是resolve状态的Promise实例,可以继续执行下面的链式调用

image.png

当没有onReject的回调的时候,就会一直往下传,不执行第一个回调,直到遇到onReject的回调为止

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    // resolve('OK');
    reject('fail')
  }, 1000);
});
p.then(value => {
  console.log(111);
}).then(value => {
  console.log(222);
  throw '失败啦!';
}).then(value => {
  console.log(333);
}).catch(reason => {
  console.warn(reason);
});

image.png

通过以上的理解我们知道,这个就是因为直接跳过了第二个then()内部的onResolve回调函数,所以不打印'失败啦',所以最后捕获的还是'fail'字符串

image.png

同样,一个resolve会跳过catch,相当于跳过then(undefined,()=>{}),直接到下一个then寻找onResolved回调函数。如下:

//创建一个新的 Promise ,且已决议
var p1 = Promise.resolve("calling next");

var p2 = p1.catch(function (reason) {
    //这个方法永远不会调用
    console.log("catch p1!");
    console.log(reason);
});

p2.then(function (value) {
    console.log("next promise's onFulfilled"); /* next promise's onFulfilled */
    console.log(value); /* calling next */
}, function (reason) {
    console.log("next promise's onRejected");
    console.log(reason);
});

image.png 总结catch只是相当于then()没有传第一个参数onResolve函数,只传了第二个onReject回调函数。在链式调用中,'reject'状态可以一直传递下去,直到遇到onReject回调函数(catch或者then第二个回调)才会被捕获,捕获后处理后,会返回'resolve'状态的Promise

中断 Promise

链式调用Promise的时候,无法从中间断开,会一直执行到最后。

如何中断? 当使用 Promisethen 链式调用时, 可以在中间中断, 不再调用后面的回调函数

中断办法: 在回调函数中返回一个 pendding 状态的 Promise 对象

当其返回 pendding 状态的 Promise 对象时,因为其状态没有改变,所以后面的 then 方法都不能执行

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
      resolve('OK');
  }, 1000);
});
p.then(value => {
  console.log(111);
  //有且只有一个方式
  return new Promise(() => {});
}).then(value => {
  console.log(222);
}).then(value => {
  console.log(333);
}).catch(reason => {
  console.warn(reason);
});

文档:

Promise.prototype.then()--MDN

Promise.prototype.catch()--MDN