前端Promise

一、Promise的理解与使用

抽象表达:

        Promise是ES6新增的一种语法,是异步编程的一种解决方案。(旧方案是单纯的使用回调函数)

        异步编程:fs文件操作、数据库操作、Ajax、定时器。

具体表达:

(1)从语法上说,promise是一种构造函数

(2)从功能上说,promise 对象用来封装一个异步操作并可以获取其成功/失败的结果值。

1. Promise的状态

 三种状态:

        pending(进行中,初始状态)

        fulfilled(成功状态,也叫resolved)

        rejected(失败状态)

        Promise只有两个过程(状态)

        (1)pending -> fulfilled : Resolved(已完成)

        (1)pending -> rejected:Rejected(已拒绝)

        通过函数resolve将状态转为fulfilled,函数reject将状态转为rejected,状态一经改变就不可以再次变化。

        一个 promise 对象只能改变一次, 无论变为成功还是失败, 都会有一个结果数据 ,成功的结果数据一般称为 value, 失败的结果数据一般称为 reason。

2. Promise的状态可变吗?

        一经改变不能再改变。

3. Promise的基本流程

前端Promise_第1张图片

4. Promise的基本使用

function doDelay(timeout){
    // 创建一个新的promise对象并返回
    return new Promise((resolve, reject) => { // 执行器函数
         // 执行异步操作任务
        setTimeout(() => {
            const time = Date.now()
            // 如果当前时间是偶数代表成功,否则失败
            if (time % 2 === 0) {
                // 如果成功,调用resolve(value)
                resolve('成功的数据,time=' + time)
            } else {
                // 如果失败,调用reject(reason)
                reject('失败的数据,time=' + time)
            }
        }, timeout);
    })
}
let promise = doDelay(1000)
//.then() 和执行器(executor)同步执行,.then() 中的回调函数异步执行
promise.then(
    value => { // 接收得到成功的value数据 onResolved
        console.log('成功的回调', value)  // 成功的回调 成功的数据
    },
    reason => { // 接收得到失败的reason数据 onRejected
        console.log('失败的回调', reason)    // 失败的回调 失败的数据
    }
)

        使用promise封装ajax异步请求。

function promiseAjax(url){
    // 创建一个新的promise对象并返回
    return new Promise((resolve, reject) => { // 执行器函数
         const xhr = new XMLHttpRequest();
         xhr.onreadystatechange=()=>{
             if(xhr.readyState!==4) return;
             const {status,response} =xhr;
             if(status>=200&&status<300){
                 resolve(JSON.parse(response))
             }else{
                 reject(new Error("失败,status:"+status))
             }
         }
         xhr.open("GET",url);
         xhr.send()
    })
}
let promise = promiseAjax('xxx')
promise.then(
    data => {
        console.log('成功的回调', data)
    },
    error => {
        console.log('失败的回调', error.message)
    }
)

二、为什么要用Promise?

       特点:

        (1)将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。流程更加清晰,代码更加优雅。

        (2)Promise对象提供统一的接口,使得控制异步操作更加容易。

        缺点:

        (1)无法取消Promise,一旦新建它就会立即执行,无法中途取消。

        (2)如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。

        (3)当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

1. 指定回调函数的方式更加灵活

        promise:启动异步任务 => 返回promise对象 => 给promise对象绑定回调函数(甚至可以在异步任务结束后指定)

2. 支持链式调用,可以解决回调地狱问题

(1)什么是回调地狱?

        回调函数嵌套使用,外部回调函数异步执行的结果是嵌套的回调执行条件。具体来说就是异步返回值又依赖于另一个异步返回值(看下面的代码)

doSomething(function(result) {
  doSomethingElse(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('Got the final result:' + finalResult)
    }, failureCallback)
  }, failureCallback)
}, failureCallback)

(2)回调地狱的缺点?

        不便于阅读、不便于异常处理

(3)解决方案?

        promise链式调用

doSomething()
  .then(result => doSomethingElse(result))
  .then(newResult => doThirdThing(newResult))
  .then(finalResult => {console.log('Got the final result:' + finalResult)})
  .catch(failureCallback)

(4)终极解决方案async/await

async function request() {
  try{
    const result = await doSomething()
    const newResult = await doSomethingElse(result)
    const finalResult = await doThirdThing(newResult)
    console.log('Got the final result:' + finalResult)
  } catch (error) {
    failureCallback(error)
  }
}

三、如何使用Promise?

1. Promise 构造函数:Promise(executor) {}

        executor 函数:同步执行 (resolve, reject) => {}

        resolve 函数:内部定义成功时调用的函数 resove(value)

        reject 函数:内部定义失败时调用的函数 reject(reason)

        说明:executor 是执行器,会在 Promise 内部立即同步回调,异步操作 resolve/reject 就在 executor 中执行

let p = new Promise((resolve,reject)=>{
    console.log(1111)
})
console.log(222)
//输出结果
//111
//222

2. Promise.prototype.then 方法:p.then(onResolved, onRejected)

        指定两个回调(成功+失败)

        onResolved 函数:成功的回调函数 (value) => {}

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

        说明:指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调,返回一个新的 promise 对象。

3. Promise.prototype.catch 方法:p.catch(onRejected)

        指定失败的回调

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

function doDelay(timeout){
    // 创建一个新的promise对象并返回
    return new Promise((resolve, reject) => { // 执行器函数
        // 执行异步操作任务
        setTimeout(() => {
            const time = Date.now()
            // 如果当前时间是偶数代表成功,否则失败
            if (time % 2 === 0) {
                // 如果成功,调用resolve(value)
                resolve('成功的数据,time=' + time)
            } else {
                // 如果失败,调用reject(reason)
                reject('失败的数据,time=' + time)
            }
        }, timeout);
    })
}
let promise = doDelay(1000)
promise.then(
    value => { // 接收得到成功的value数据 onResolved
        console.log('成功的回调', value)  // 成功的回调 成功的数据
    },
).catch(
    reason => { // 接收得到失败的reason数据 onRejected
        console.log('失败的回调', reason)    // 失败的回调 失败的数据
    }
)

        说明:这是then() 的语法糖,相当于 then(undefined, onRejected)

4. Promise.resolve 方法:Promise.resolve(value)

        是promise这个函数对象的,并不属于实例对象。

        value:将被 Promise 对象解析的参数,也可以是一个成功或失败的 Promise 对象

        返回:返回一个带着给定值解析过的 Promise 对象,如果参数本身就是一个 Promise 对象,则直接返回这个 Promise 对象。

        作用:快速得到Promise对象,一个封装一个值,将这个值转换为Promise对象

        (1)如果传入的参数为 非Promise类型的对象, 则返回的结果为成功promise对象

let p1 = Promise.resolve(521);
console.log(p1); // Promise {: 521}

        (2)如果传入的参数为 Promise 对象, 则参数的结果决定了 resolve 的结果

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

前端Promise_第2张图片

5. Promise.reject 方法:Promise.resolve(reason)

        reason:失败的原因

        说明:返回一个失败的 promise 对象

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

console.log(p);
console.log(p2);
console.log(p3);

        Promise.resolve()/Promise.reject() 方法就是一个语法糖

        用来快速得到Promise对象

前端Promise_第3张图片

6. Promise.all 方法:Promise.all(iterable)

        iterable:包含 n 个 promise 的可迭代对象,如 Array 或 String

        说明:返回一个新的 promise,只有所有的 promise 都成功才成功,只要有一个失败了就直接失败。

 let p1= new Promise((resolve,reject)=>{
        resolve("success")
    })
    let p2 = Promise.resolve('success')
    let p3 = Promise.resolve('yet')
    let p4 = Promise.reject('error')
    const res = Promise.all([p1,p2,p3]);
    res.then(
        value => {
            console.log('res成功的回调', value)
        },
        reason => {
            console.log('res失败的回调', reason)
        }
    )
    const res2 = Promise.all([p1,p2,p3,p4]);
    res2.then(//p4失败,所以res2失败
        value => {
            console.log('res2成功的回调', value)
        },
        reason => {
            console.log('res2失败的回调', reason)
        }
    )let p1= new Promise((resolve,reject)=>{
    resolve("success")
})
let p2 = Promise.resolve('success')
let p3 = Promise.resolve('yet')
let p4 = Promise.reject('error')
const res = Promise.all([p1,p2,p3]);
const res2 = Promise.all([p1,p2,p3,p4]);
console.log(res)
console.log(res2)

前端Promise_第4张图片

7. Promise.race方法:Promise.race(iterable)

        iterable:包含 n 个 promise 的可迭代对象,如 Array 或 String

        说明:返回一个新的 promise,第一个完成的 promise 的结果状态就是最终的结果状态,谁先完成就输出谁(不管是成功还是失败)

let p1= new Promise((resolve,reject)=>{
    setTimeout(() => {
        resolve(1)
    }, 1000)
})
let p2 = Promise.resolve(2)
let p3 = Promise.resolve(3)
let p4 = Promise.reject(4)
//谁先完成就输出谁(不管是成功还是失败)
const res = Promise.race([p1,p2,p3,p4]);
res.then(//2先执行,输出2
    value => {//res成功的回调 2
        console.log('res成功的回调', value)
    },
    reason => {
        console.log('res失败的回调', reason)
    }
)
const res2 = Promise.all([p4,p2,p3,p1]);
res2.then(//4先执行,输出4
    value => {//res2失败的回调 4
        console.log('res2成功的回调', value)
    },
    reason => {
        console.log('res2失败的回调', reason)
    }
)

8.Promise.any()

        区别于Promise.all(), Promise.any() 只要有一个成功,就返回成功的promise,如果没有一个成功,就返回一个失败的promise。

9.Promise.allSelected()

        Promise.allSelected([]).then(res => {}).catch(err => {})

        返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const res= Promise.allSettled([promise1,promise2])
res.then(
    results=>results.forEach(result=>console.log(result))
)

四、Promise的几个关键问题

1. 如何改变 promise 的状态?

        (1)resolve(value):如果当前是 pending 就会变为 resolved

        (2)reject(reason):如果当前是 pending 就会变为 rejected

        (3)抛出异常:如果当前是 pending 就会变为 rejected

const p = new Promise((resolve, reject) => {
  //resolve(1) // promise变为resolved成功状态
  //reject(2) // promise变为rejected失败状态
  throw new Error('出错了') // 抛出异常,promise变为rejected失败状态,reason为抛出的error
})
p.then(
  value => {},
  reason => {console.log('reason',reason)}
)
// reason Error:出错了

2. 一个 promise 指定多个成功/失败回调函数,都会调用吗?

        当 promise 改变为对应状态时都会调用

const p = new Promise((resolve, reject) => {
    resolve(1)
    // reject(2)
})
p.then(
    value => {console.log('value',value)},
    reason => {console.log('reason',reason)}
)
p.then(
    value => {console.log('value2',value)},
    reason => {console.log('reason2',reason)}
)
// value 1
// value 1
// reason 2
// reason2 2

3. 改变 promise 状态(resolve、reject、throw)和指定回调函数(then,catch)谁先谁后?

        都有可能,常规是先指定回调再改变状态,但也可以先改状态再指定回调

        如何先改状态再指定回调?

        (1)在执行器中直接调用 resolve()/reject()

        (2)延迟更长时间才调用 then()

let p = new Promise((resolve, reject) => {
    resolve('OK');//执行器函数中的任务是同步任务,直接调动resolve,这是先改变状态,后执行回调
    // setTimeout(() => {
    //resolve('OK');
    // }, 1000); // 有异步就先指定回调,否则先改变状态
});
p.then(value => {
    console.log(value);
},reason=>{

})

        什么时候才能得到数据?

        (1)如果先指定的回调,那当状态发生改变时,回调函数就会调用得到数据

        (2)如果先改变的状态,那当指定回调时,回调函数就会调用得到数据

new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1) // 改变状态
  }, 1000)
}).then( // 指定回调函数 (先指定)
  value => {},
  reason =>{}
)

        此时,先指定回调函数,保存当前指定的回调函数;后改变状态(同时指定数据),然后异步执行之前保存的回调函数。

new Promise((resolve, reject) => {
  resolve(1) // 改变状态
}).then( // 指定回调函数
  value => {},
  reason =>{}
)

        这种写法,先改变的状态(同时指定数据),后指定回调函数(不需要再保存),直接异步执行回调函数

4. promise.then() 返回的新 promise 的结果状态由什么决定?

        (1)简单表达:由 then() 指定的回调函数执行的结果决定

let p = new Promise((resolve, reject) => {
  resolve('ok');
});
//执行 then 方法
let result = p.then(value => {
  console.log(value);
}, reason => {
  console.warn(reason);
});

console.log(result);

前端Promise_第5张图片

        (2)详细表达:

        ① 如果抛出异常,新 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);

        ② 如果返回的是非 promise 的任意值,新 promise 变为 resolved,value 为返回的值

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

        ③ 如果返回的是另一个新 promise,此 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);

5.promise 如何串联多个操作任务?

        (1)promise 的 then() 返回一个新的 promise,可以并成 then() 的链式调用

        (2)通过 then 的链式调用串联多个同步/异步任务

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

p.then(value => {
  return new Promise((resolve, reject) => {
      resolve("success");
  });
}).then(value => {
  console.log(value); // success
}).then(value => {
  console.log(value); // undefined
})
new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('执行任务1(异步)')
    resolve(1)
  }, 1000)
}).then(
  value => {
    console.log('任务1的结果', value)
    console.log('执行任务2(同步)')
    return 2 // 同步任务直接return返回结果
  }
).then(
  value => {
    console.log('任务2的结果', value)
    return new Promise((resolve, reject) => { // 异步任务需要包裹在Promise对象中
      setTimeout(() => {
        console.log('执行任务3(异步)')
        resolve(3)
      }, 1000)
    })
  }
).then(
  value => {
    console.log('任务3的结果', value)
  }
)
// 执行任务1(异步)
// 任务1的结果 1
// 执行任务2(同步)
// 任务2的结果 2
// 执行任务3(异步)
// 任务3的结果 3

6.Promise 异常穿透(传透)?

        在最后加一个catch方法

        (1)当使用 promise 的 then 链式调用时,可以在最后指定失败的回调

        (2)前面任何操作出了异常,都会传到最后失败的回调中处理

new Promise((resolve, reject) => {
   //resolve(1)
   reject(1)
}).then(
  value => {
    console.log('onResolved1()', value)
    return 2
  }
).then(
  value => {
    console.log('onResolved2()', value)
    return 3
  }
).then(
  value => {
    console.log('onResolved3()', value)
  }
).catch(
  reason => {
    console.log('onRejected1()', reason)
  }
)
// onRejected1() 1

相当于这种写法:多写了很多reason => {throw reason}

new Promise((resolve, reject) => {
   //resolve(1)
   reject(1)
}).then(
  value => {
    console.log('onResolved1()', value)
    return 2
  },
  reason => {throw reason} // 抛出失败的结果reason
).then(
  value => {
    console.log('onResolved2()', value)
    return 3
  },
  reason => {throw reason} // 抛出失败的结果reason
).then(
  value => {
    console.log('onResolved3()', value)
  },
  reason => {throw reason} // 抛出失败的结果reason
).catch(
  reason => {
    console.log('onRejected1()', reason)
  }
)
// onRejected1() 1

7.中断 promise 链?

        当使用 promise 的 then 链式调用时,在中间中断,不再调用后面的回调函数

        办法:在回调函数中返回一个 pending 状态的 promise 对象,return new Promise(() => {})

new Promise((resolve, reject) => {
   //resolve(1)
   reject(1)
}).then(
  value => {
    console.log('onResolved1()', value)
    return 2
  }
).then(
  value => {
    console.log('onResolved2()', value)
    return 3
  }
).then(
  value => {
    console.log('onResolved3()', value)
  }
).catch(
  reason => {
    console.log('onRejected1()', reason)
  }
).then(
  value => {
    console.log('onResolved4()', value)
  },
  reason => {
    console.log('onRejected2()', reason)
  }
)
// onRejected1() 1
// onResolved4() undefined

        为了在 catch 中就中断执行,可以这样写,return new Promise(() => {})

new Promise((resolve, reject) => {
   //resolve(1)
   reject(1)
}).then(
  value => {
    console.log('onResolved1()', value)
    return 2
  }
).then(
  value => {
    console.log('onResolved2()', value)
    return 3
  }
).then(
  value => {
    console.log('onResolved3()', value)
  }
).catch(
  reason => {
    console.log('onRejected1()', reason)
    return new Promise(() => {}) // 返回一个pending的promise
  }
).then(
  value => {
    console.log('onResolved4()', value)
  },
  reason => {
    console.log('onRejected2()', reason)
  }
)
// onRejected1() 1

        在 catch 中返回一个新的 promise,且这个 promise 没有结果。

        由于,返回的新的 promise 结果决定了后面 then 中的结果,所以后面的 then 中也没有结果。

        这就实现了中断 promise链的效果。

五、 async await 和Promise

1.执行async函数,返回的都是Promise对象

async function test1() {
    return 1;
};
async function test2() {
    return Promise.resolve(1);
};
const res1 = test1();
const res2 = test2();

console.log('res1', res1);
console.log('res2', res2);

前端Promise_第6张图片

2.Promise.then 成功的情况,对应的是await

async function test3() {
    const p3 = Promise.resolve(3);
    p3.then(data => {
        console.log('data',data);
    });
    const data = await p3;
    console.log(data);
}
test3();
//data 3
//3

3.Promise.catch 异常的情况, 对于try…catch…

async function test3() {
    const p3 = Promise.reject(3);
    p3.catch(data => {
        console.log('data',data);
    });
    try {
        const data = await p3;
        console.log('data',data);
    } catch (err) {
        console.log('err', err);
    }
}
test3();
//data 3
// error 3

4. 如何让Promise顺序执行?应用场景是什么?

const p1 = new Promise(resolve => {
    setTimeout(()=> {
        resolve(1);
    }, 3000);
});
const p2 = new Promise(resolve => {
    setTimeout(()=> {
        resolve(2);
    }, 2000);
});
const p3 = new Promise(resolve => {
    setTimeout(()=> {
        resolve(3);
    }, 1000);
});
async function execute() {
    await p1.then(data => console.log(data));
    await p2.then(data => console.log(data));
    await p3.then(data => console.log(data));
};
execute();
//1
//2
//3

六、常见面试题

Promise面试题汇总_CUG-GZ的博客-CSDN博客_promise面试题

你可能感兴趣的